Неповторяющиеся случайные числа в Objective-C - PullRequest
7 голосов
/ 24 октября 2009

Я использую

for (int i = 1, i<100, i++)
    int i = arc4random() % array count;

но я получаю повторения каждый раз. Как я могу заполнить выбранное значение int из диапазона, чтобы, когда программа зациклилась, я не получил никакого дублирования?

Ответы [ 6 ]

16 голосов
/ 24 октября 2009

Звучит так, будто вы хотите перетасовать сет, а не «истинную» случайность. Просто создайте массив, в котором все позиции соответствуют номерам, и инициализируйте счетчик:

num[ 0] =  0
num[ 1] =  1
: :
num[99] = 99
numNums = 100

Затем, когда вы хотите случайное число, используйте следующий метод:

idx = rnd (numNums);       // return value 0 through numNums-1
val = num[idx];            // get then number at that position.
num[idx] = val[numNums-1]; // remove it from pool by overwriting with highest
numNums--;                 //   and removing the highest position from pool.
return val;                // give it back to caller.

Это вернет случайное значение из постоянно уменьшающегося пула, гарантируя отсутствие повторов. Вам, конечно, придется остерегаться того, что пул работает до нулевого размера, и разумно переинициализировать пул.

Это более детерминированное решение, чем сохранение списка используемых номеров и продолжение цикла, пока вы не найдете его в этом списке. Производительность такого алгоритма будет ухудшаться по мере уменьшения пула.

Функция C, использующая статические значения, примерно такая, должна сработать. Звони с

int i = myRandom (200);

для установки пула (с любым числом от нуля или больше, определяющим размер) или

int i = myRandom (-1);

чтобы получить следующий номер из пула (достаточно любого отрицательного числа). Если функция не может выделить достаточно памяти, она вернет -2. Если в пуле не осталось чисел, он вернет -1 (в этот момент вы можете повторно инициализировать пул, если хотите). Вот функция с основным модулем тестирования, которую вы можете попробовать:

#include <stdio.h>
#include <stdlib.h>

#define ERR_NO_NUM -1
#define ERR_NO_MEM -2

int myRandom (int size) {
    int i, n;
    static int numNums = 0;
    static int *numArr = NULL;

    // Initialize with a specific size.

    if (size >= 0) {
        if (numArr != NULL)
            free (numArr);
        if ((numArr = malloc (sizeof(int) * size)) == NULL)
            return ERR_NO_MEM;
        for (i = 0; i  < size; i++)
            numArr[i] = i;
        numNums = size;
    }

    // Error if no numbers left in pool.

    if (numNums == 0)
       return ERR_NO_NUM;

    // Get random number from pool and remove it (rnd in this
    //   case returns a number between 0 and numNums-1 inclusive).

    n = rand() % numNums;
    i = numArr[n];
    numArr[n] = numArr[numNums-1];
    numNums--;
    if (numNums == 0) {
        free (numArr);
        numArr = 0;
    }

    return i;
}

int main (void) {
    int i;

    srand (time (NULL));
    i = myRandom (20);
    while (i >= 0) {
        printf ("Number = %3d\n", i);
        i = myRandom (-1);
    }
    printf ("Final  = %3d\n", i);
    return 0;
}

А вот результат одного прогона:

Number =  19
Number =  10
Number =   2
Number =  15
Number =   0
Number =   6
Number =   1
Number =   3
Number =  17
Number =  14
Number =  12
Number =  18
Number =   4
Number =   9
Number =   7
Number =   8
Number =  16
Number =   5
Number =  11
Number =  13
Final  =  -1

Имейте в виду, что, поскольку в нем используется статика, звонить из двух разных мест небезопасно, если они хотят поддерживать свои собственные отдельные пулы. Если бы это было так, статика была бы заменена буфером (счетчик хранения и пул), который бы "принадлежал" вызывающей стороне (для этой цели можно было бы передать двойной указатель).

И, если вы ищете версию с несколькими пулами, я включу ее здесь для полноты.

#include <stdio.h>
#include <stdlib.h>

#define ERR_NO_NUM -1
#define ERR_NO_MEM -2

int myRandom (int size, int *ppPool[]) {
    int i, n;

    // Initialize with a specific size.

    if (size >= 0) {
        if (*ppPool != NULL)
            free (*ppPool);
        if ((*ppPool = malloc (sizeof(int) * (size + 1))) == NULL)
            return ERR_NO_MEM;
        (*ppPool)[0] = size;
        for (i = 0; i  < size; i++) {
            (*ppPool)[i+1] = i;
        }
    }

    // Error if no numbers left in pool.

    if (*ppPool == NULL)
       return ERR_NO_NUM;

    // Get random number from pool and remove it (rnd in this
    //   case returns a number between 0 and numNums-1 inclusive).

    n = rand() % (*ppPool)[0];
    i = (*ppPool)[n+1];
    (*ppPool)[n+1] = (*ppPool)[(*ppPool)[0]];
    (*ppPool)[0]--;
    if ((*ppPool)[0] == 0) {
        free (*ppPool);
        *ppPool = NULL;
    }

    return i;
}

int main (void) {
    int i;
    int *pPool;

    srand (time (NULL));
    pPool = NULL;
    i = myRandom (20, &pPool);
    while (i >= 0) {
        printf ("Number = %3d\n", i);
        i = myRandom (-1, &pPool);
    }
    printf ("Final  = %3d\n", i);
    return 0;
}

Как видно из измененного main(), вам нужно сначала инициализировать указатель int на NULL, а затем передать его адрес функции myRandom(). Это позволяет каждому клиенту (расположение в коде) иметь свой собственный пул, который автоматически выделяется и освобождается, хотя вы все равно можете совместно использовать пулы, если хотите.

1 голос
/ 02 декабря 2012

Вы можете использовать Форматно-сохраняющее шифрование для шифрования счетчика. Ваш счетчик просто идет от 0 вверх и шифрование использует ключ по вашему выбору, чтобы превратить его в, казалось бы, случайное значение любого желаемого радиуса и ширины.

Блочные шифры обычно имеют фиксированный размер блока, например 64 или 128 бит. Но шифрование, сохраняющее формат, позволяет вам взять стандартный шифр, такой как AES, и создать шифр меньшей ширины с любой требуемой шириной и шириной (например, основание 2, ширина 16) с алгоритмом, который все еще криптографически устойчив.

Гарантируется, что никогда не будет коллизий (поскольку криптографические алгоритмы создают отображение 1: 1). Он также обратим (двухстороннее сопоставление), поэтому вы можете взять полученное число и вернуться к значению счетчика, с которого вы начали.

AES-FFX является одним из предложенных стандартных методов для достижения этой цели. Я экспериментировал с некоторым базовым кодом Python, который основан на идее AES-FFX, хотя и не полностью совместим - см. Код Python здесь . Это может, например, зашифруйте счетчик случайным 7-значным десятичным числом или 16-разрядным числом.

0 голосов
/ 24 октября 2009

В дополнение к использованию вторичного массива для хранения уже сгенерированных случайных чисел, вызывается случайное нет. Функция высева перед каждым случайным вызовом отсутствует. Функция генерации может помочь генерировать различные последовательности. случайных чисел в каждом запуске.

0 голосов
/ 24 октября 2009

Лучший способ сделать это - создать массив для уже используемых чисел. После того, как случайное число было создано, добавьте его в массив. Затем при создании другого случайного числа убедитесь, что его нет в массиве используемых чисел.

0 голосов
/ 24 октября 2009

Не полагаясь на внешние стохастические процессы, такие как радиоактивный распад или пользовательский ввод, компьютеры всегда будут генерировать псевдослучайные числа - то есть числа, которые имеют многие статистические свойства случайных чисел, но повторяются в последовательностях.

Это объясняет предложения рандомизировать вывод компьютера путем перемешивания.

Отказ от ранее использованных чисел может искусственно удлинить последовательность, но за счет статистики, которая создает впечатление случайности.

0 голосов
/ 24 октября 2009

Вам необходимо отслеживать числа, которые вы уже использовали (например, в массиве). Получить случайное число и выбросить его, если оно уже использовалось.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...