Неповторяющийся случайный поиск в диапазоне Алгоритм - PullRequest
1 голос
/ 19 августа 2011

Я ищу эффективный алгоритм, который генерирует случайные значения в пределах диапазона, без повторений.

В псевдокоде: (в классе Rand)

  Rand(long from, long to) {
    this.from = from;
    this.to = to;
    // ...
  }

  long getNumber() {

    // returns a random number in the [from, to] range
    //  which has never been returned before
  }

Использование:

  Rand r = new Rand(1, 100000000);

  long x = r.getNumber();
  long y = r.getNumber();
  ...

Числа, возвращаемые из r.getNumber (), всегда должны отличаться от ранее возвращенных.Конечно, если было возвращено to - from + 1 чисел, алгоритм должен запуститься снова (или ошибиться - в любом случае это не важно).

Обратите внимание, что диапазон может быть очень большим, и, следовательно, случайным образом организованный массив (содержащийвначале [from, to] чисел), вероятно, переполнит память.

Ответы [ 7 ]

5 голосов
/ 19 августа 2011

Шифр ​​- это взаимно-однозначное отображение, в противном случае его невозможно расшифровать. Следовательно, любой блочный шифр отобразит числа 0, 1, 2, 3, 4, 5, ... в разные n-битные числа, где n - размер блока шифра в битах.

Относительно легко собрать простой 4-раундовый шифр Фейстеля с любым (даже) размером блока, который вы хотите. Только с четырьмя раундами это было бы быстро, но небезопасно. В качестве альтернативы используйте шифр Hasty Pudding , который может иметь практически любой размер блока.

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

1 голос
/ 08 апреля 2013

@ Россум сказал:

Шифр ​​- это взаимно-однозначное отображение, в противном случае его невозможно расшифровать. Следовательно, любой блочный шифр отобразит числа 0, 1, 2, 3, 4, 5, ... в разные n-битные числа, где n - размер блока шифра в битах.

Таким образом, даже xor-шифрование или битовое реверсирование подойдет для некоторых целей.

А вот функция php, использующая xor и побитовое обратное преобразование в качестве простого шифрования 1: 1.

Это генератор псевдослучайных чисел с гарантированным заполнением всех значений без идентичных значений. Вы предоставляете n: 0..63, и это дает случайное 0..63.

Он принимает только 2 ^ i диапазонов, таких как 0..63, 0..127 и т. Д.

Не криптографически безопасен и т. Д., Просто случайный.

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

 function math_random_filled($n,$bits,$seed)
 {
   //bits: examples: 6=0..63, 8=0..255, 10: 0..1023
   //n: 0<= n <2^$bits
   //seed: any string or number

   //generate xor: crc32 + bit mask
   $xor= crc32($seed.'internalseed') & ~(-1<<$bits);
   //xor
   $r=intval($n)^$xor;
   //bitwise reverse
   $r=bindec(strrev(str_pad(decbin($r),$bits,'0',STR_PAD_LEFT)));
   return $r;
 }

 //demonstration
 $bits=6;
 $min=0;
 $max=pow(2,$bits)-1;
 $count=$max-$min+1;
 for($n=0;$n<=$max;$n++)
 {
   $r=math_random_filled($n,$bits,$seed='someseed');
   echo " $r ";
   $ar[$r]=1;
 }


 $set=0;
 for($n=0;$n<=$max;$n++)
   if(isset($ar[$n])) $set++;


 echo "\n"."bits: $bits,  count: $count, set: ". $set."\n\n";

пример вывода:

 37  5  53  21  45  13  61  29  33  1  49  17  41  9  57  25  39  7  55  23  47  15  63  31  35  3  51  19  43  11  59  27  36  4  52  20  44  12  60  28  32  0  48  16  40  8  56  24  38  6  54  22  46  14  62  30  34  2  50  18  42  10  58  26 

bits: 6,  count: 64, set: 64

Вы можете проверить код здесь в песочнице php

А вот еще один, но допускает любой диапазон, а не только степени 2.

function math_random_filled_arithmetical($n,$max,$seed)
    {
    /*
    - produces 0..max, repeatable, unique, one-to-one mapped random numbers
    - uses arithmetic operations to imitate randomness
    - $n: 0<=$n=<$max
    - $max: any integer, not only power of two
    - $seed: any string or number
    */
    $n=intval($n);
    $max=intval($max);
    $opt1=$n;
    $opt2=$max-$n;
    $n2=min($opt1,$opt2);
    $reverseit=crc32($seed.'internalseed'.$n2)&1;
    if($opt1>$opt2) $reverseit=!$reverseit;
    $max2=floor(intval($max-1)/2);
    //echo "n:$n, max:$max,n2:$n2,max2:$max2,reverseit:$reverseit\n";
    if($max>=3)
    if($opt1!=$opt2)
        $n2=math_random_filled_arithmetical($n2,$max2,$seed.'*');
    $res=$reverseit? $max-$n2:$n2;
    $res=intval(fmod($res+(crc32($seed)&(1<<30)),$max+1));
    //echo "n:$n, max:$max, res:$res\n";
    return $res;
    }


//demonstration
$max=41;//-- test a max value 
for($n=0;$n<=$max;$n++)
    {
    $r=math_random_filled_arithmetical($n,$max,$seed='someseed');
    $ar[$r]=1;
    echo " $r ";
    }
$filled=0;
for($n=0;$n<=$max;$n++)
    if(isset($ar[$n])) $filled++;
echo "\n count: ".($max+1).", filled: ". $filled."\n";

пример вывода:

 20  19  18  17  33  32  37  36  14  13  31  34  35  26  16  11  12  3  39  40  0  41  1  2  38  29  30  25  15  6  7  10  28  27  5  4  9  8  24  23  22  21 
 count: 42, filled: 42

связанная песочница php находится здесь

1 голос
/ 19 августа 2011

Я буду притворяться, что вы просите решение в R (в соответствии с тегом). То, что вы пытаетесь сделать, это образец без замены. В R есть функция с именем sample. Здесь я выбираю вектор из 30 значений (1, 2, 3 ... 30), и когда я рисую число, оно не заменяется. Вы можете сделать это воспроизводимым на других машинах, установив seed (см. set.seed).

Я запускал это несколько раз, чтобы показать случайность.

> sample(x = 1:30, size = 10, replace = FALSE)
 [1]  9 16 13 20 12  3  1  5 28  7
> sample(x = 1:30, size = 10, replace = FALSE)
 [1] 22 11 26 29 20  1  3  6  7 10
> sample(x = 1:30, size = 10, replace = FALSE)
 [1]  1 11 16  7 22 26  3 25  8  9
> sample(x = 1:30, size = 10, replace = FALSE)
 [1]  7 17  3 22 21 24 27 12 28  2
> sample(x = 1:30, size = 10, replace = FALSE)
 [1] 30 21 23  2 27 24  3 18 25 19
> sample(x = 1:30, size = 10, replace = FALSE)
 [1]  4  6 11 16 26  8 17 22 23 25
1 голос
/ 19 августа 2011

Вы можете продолжить этот путь (в Python):

Создайте xrange, выберите из него k случайных элементов и используйте его как RanndomGenerator:

import random

def randomFromTo(FROM,TO,k): #k is the number of sample you want to generate
    m= random.sample(xrange(FROM,TO),k)
    return (i for i in m)

Ваш Генератор сгенерирует все числа случайным образом от ОТ до ТО и потерпит неудачу, когда он сгенерировал более k чисел:

с этим примером вы получите:

RandomGenerator=randomFromTo(10,1000000000,12)

for k in range(12): 
    print RandomGenerator.next()

вы получите

57625960
50621599
2891457
56292598
54608718
45258991
24112743
55282480
28873528
1120483
56876700
98173231
1 голос
/ 19 августа 2011

Некоторые мысли для начальной точки:

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

2) Существуют коммутирующие сети, которые позволяют вам производить все возможные перестановки для N объектов для специальных N - один пример - http://en.wikipedia.org/wiki/Clos_network#Bene.C5.A1_network_.28m_.3D_n_.3D_2.29 (забавный URL - сеть Benes).Вы можете получить произвольную перестановку для N объектов, где N, я думаю, должен быть степенью 2, установив переключатели в случайном порядке.Поскольку будет K переключателей, есть 2 ^ K способов их установки, что означает, что у вас нет M!перестановки с равной вероятностью, но, возможно, вы не будете возражать против этого или сможете минимизировать неслучайность, повторяя это несколько раз, или что-то в этом роде.

3) Если вы готовы достичь почти случайности, применяя множество различныхбазовые перестановки много раз, вы можете попробовать поочередно добавить mod N во весь диапазон, а затем разбить диапазон на поддиапазоны и, например, для некоторого отрезка значений p-1 в пределах диапазона применить перестановку, полученную умножением на некоторый k-режим p.Надежда будет состоять в том, что хотя каждый отдельный шаг является довольно простым, если применить их достаточно и сделать их достаточно разнообразными, результат будет близок к случайному, особенно если вы выбираете параметры случайным образом.

1 голос
/ 19 августа 2011

Если вам не требуется, чтобы каждое число из интервала появлялось в конечном итоге, вы можете использовать линейный конгруэнтный генератор :

int getNumber() {
    seed = (seed * A + C) mod (to-from);
    return seed + to;
}

Это периодическое издание, новый период начинается, когда семенастановится равным начальному значению, а продолжительность периода зависит от выбора A и C.

Плюсы: O (1) время и пространство, минусы: не каждое число из интервала появится.

Для интервалов длиной 2 ^ m посмотрите на http://en.wikipedia.org/wiki/Linear_feedback_shift_register Я не использовал его, но википедия говорит, что это возможно, чтобы быть максимальной длины, то есть вы можете иметь все числа (кроме одного) появляются в выводе.

1 голос
/ 19 августа 2011

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

РЕДАКТИРОВАТЬ: Еще несколько мыслей.

Для действительно огромных диапазонов, даже это не обеспечит хорошую производительность при ограничении памяти.Одной из идей может быть сохранение кандидатов не в виде списка чисел, а в виде интервала.Итак, изначально вы выбираете между и, получая х1.В следующий раз выберите число из первого подинтервала или второго с вероятностью, пропорциональной длине интервала.На каждом шаге исключайте интервалы, которые имеют нулевую длину.Это требует хранения M + 2 целых чисел (в худшем случае), где M - число вытяжек или N / 2 асимптотически для больших N (в худшем случае), где N - начальный размер интервала.Кто-то может перепроверить меня, хотя.

...