Алгоритм генерации случайного числа

Я хочу создать случайное число и выдать его в таблицу в базе данных для определенного user_id. Уловка, то же число нельзя использовать дважды. Там миллион способов сделать это, но я надеюсь, что у кого-то, кто очень увлекается алгоритмами, есть умный способ решения проблемы в элегантном решении, в котором выполняются следующие критерии:

1) Выполняется наименьшее количество запросов к базе данных. 2) Выполняется наименьшее количество обхода структуры данных в памяти.

По существу, идея состоит в том, чтобы сделать следующее

1) Создайте случайное число от 0 до 9999999
2) Проверьте базу данных, чтобы узнать, существует ли номер   ИЛИ
2) Запросить базу данных для всех номеров
3) Посмотрите, соответствует ли возвращаемый результат тому, что прибыло из db
4) Если он совпадает, повторите шаг 1, если нет, проблема решена.

Спасибо.

Ответ 1

Нет, ваш алгоритм не масштабируется. То, что я делал раньше, - это серийно выпускать номера (+1 каждый раз), а затем передавать их через операцию XOR, чтобы перемешивать биты, тем самым давая мне, казалось бы, случайные числа. Конечно, они на самом деле не случайны, но они выглядят так, как пользователи.


[Изменить] Дополнительная информация

Эта логика алгоритма идет так, что вы используете известную последовательность для генерировать уникальные числа, а затем вы детерминистически ими манипулируете, поэтому они больше не выглядят серийно. Общее решение заключается в использовании некоторая форма шифрования, которая в моем случае была триггером XOR, потому что как можно быстрее, и он выполняет гарантию того, что числа никогда не столкнется.

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

[Спасибо Адам Лисс и CesarB для экспансии в решении]

Ответ 2

Почему бы вам просто не использовать GUID? Большинство языков должны иметь встроенный способ сделать это. Он гарантированно уникален (с очень разумными пределами).

Ответ 3

Хотите получить более высокое решение?

Я предполагаю, что случайность не предназначена для качества шифрования, а достаточно для того, чтобы препятствовать угадыванию долговечности пользователя, пользователем_id.

Во время разработки сгенерируйте список из 10 миллионов номеров в строковой форме.

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

Передайте их в инструмент, который генерирует функции Perfect Hash, такие как gperf.

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

Ответ 4

Попробовать инструкцию в mysql SELECT CAST (RAND() * 1000000 AS INT)

Ответ 5

Предполагая, что:

  • Случайность необходима для уникальности, а не для безопасности
  • Ваш user_id 32 бит
  • Ваш предел 9999999 был всего лишь примером

Вы могли бы сделать что-то простое, как случайное число в виде 64-битного целого числа, причем верхние 32 бита содержат метку времени (при вставке строки) и нижние 32 бита user_id. Это было бы уникально даже для нескольких строк с одним и тем же пользователем, если вы используете соответствующее разрешение на своей временной отметке в зависимости от того, как часто вы добавляете новые строки для одного и того же пользователя. Объедините с уникальным ограничением на случайный столбец и поймайте любую такую ​​ошибку в своей логике, а затем просто повторите попытку.

Ответ 6

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

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

Ответ 7

Мой опыт заключался в просто использовании RNG в PHP. Я обнаружил, что с использованием определенного размера числа (я использую int, поэтому у меня максимум 4G). Я провел несколько тестов и обнаружил, что в среднем в 500 000 итераций я получил 120 отдельных дубликатов. Я никогда не получал три повторения после запуска цикла кучу раз. Мое "решение" должно было просто вставить и проверить, не сработало ли оно, затем сгенерировать новый идентификатор и снова вернуться.

Мой совет заключается в том, чтобы сделать то же самое и посмотреть, какова ваша ставка столкновения & c и посмотреть, приемлемо ли это для вашего случая.

Это не оптимально, поэтому, если у кого-то есть предложения, которые я тоже смотрю:)

EDIT: я был ограничен 5-значным идентификатором ([a-zA-z0-9] {5,5}), тем больше идентификатор (больше комбинации, несколько коллизий). Например, md5 электронной почты почти никогда не конфликтует.

Ответ 8

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

Однако:

<?php
//Lets assume we already have a connection to the db
$sql = "SELECT randField FROM tableName";
$result = mysql_query($sql);
$array = array();
while($row = mysql_fetch_assoc($result))
 {
   $array[] = $row['randField'];
 }
while(True)
 {
   $rand = rand(0, 999999);
   if(!in_array($rand))
     {
       //This number is not in the db so use it!
       break;
     }
 }
?>

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

Ответ 9

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

Кстати, почему бы просто не выпустить идентификатор пользователя последовательно?

Ответ 10

Мне нравится идея Oddthinking, но вместо того, чтобы выбирать самую сильную хэш-функцию в мире, вы могли бы просто:

  • Создайте MD5 первых 10 миллионов чисел (выраженных как строки, + некоторые соли)
  • Проверяйте дубликаты в автономном режиме, т.е. перед тем, как приступить к работе (я думаю, их не будет)
  • Хранить дубликаты в массиве где-то
  • При запуске приложения загрузите массив
  • Если вы хотите вставить идентификатор, выберите следующий номер, вычислите его MD5, проверьте, находится ли он в массиве, и если он не использует его как идентификатор в базе данных. В противном случае выберите следующий номер

MD5 быстр, и проверка того, принадлежит ли строка массиву, позволит вам выбрать SELECT.

Ответ 11

Я уже ранее писал статью об этом. Он принимает тот же подход, что и Роберт Гулд, но дополнительно показывает, как сократить блок-шифр до подходящей длины с помощью xor folding, а затем как сгенерировать перестановки в диапазоне, который не равен 2, сохраняя при этом свойство уникальности.

Ответ 12

Если вы действительно хотите получить "случайные" цифры от 0 до 9 999 999, тогда решение должно выполнить "рандомизацию" один раз, а затем сохранить результат на ваш диск.

Не сложно получить желаемый результат, но я думаю, что это больше похоже на "сделать длинный список с числами", чем "получить случайное число".

$array = range(0, 9999999);
$numbers = shuffle($array);

Вам также нужен указатель на текущую позицию в $numbers (сохранить его в базе данных); начинайте с 0 и увеличивайте его каждый раз, когда вам нужен новый номер. (Или вы можете использовать array_shift() или array_pop(), если вы не хотите использовать указатели.)

Ответ 13

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

Простой PRNG, который делает это, называется " Linear Congruential 'PRNG, который выполняет итерацию формулы:

X(i) = AX(i-1)|M

Используя правую пару факторов, вы можете получить период 2 ^ 30 (приблизительно 1 миллиард) от простого PRNG с 32-разрядным аккумулятором. Обратите внимание, что вам понадобится временная переменная длиной 64 бит, чтобы удерживать промежуточную "AX" часть вычисления. Большинство, если не все компиляторы C будут поддерживать этот тип данных. Вы также должны иметь возможность делать это с числовым типом данных на большинстве диалектов SQL.

При правильных значениях A и M мы можем получить генератор случайных чисел с хорошими статистическими и геометрическими свойствами. Известная статья об этом написана Фишманом и Муром.

При M = 2 ^ 31 - 1 мы можем использовать значения A ниже, чтобы получить PRNG с хорошим длинным периодом (2 ^ 30 IIRC).

Хорошие значения A:

742,938,285  
950,706,376  
1,226,874,159  
62,089,911  
1,343,714,438   

Обратите внимание, что этот тип генератора (по определению) не криптографически защищен. Если вы знаете последнее число, сгенерированное из него, вы можете предсказать, что он будет делать дальше. К сожалению, я считаю, что вы не можете получить криптографическую безопасность и гарантированную неповторяемость в одно и то же время. Для того чтобы PRNG был криптографически защищен (например, Blum Blum Shub), он не может выставить достаточное состояние в сгенерированном номере, чтобы разрешить следующее число в последовательности для прогнозирования. Поэтому внутреннее состояние шире, чем сгенерированное число, и (для обеспечения хорошей безопасности) период будет длиннее, чем количество возможных значений, которые могут быть сгенерированы. Это означает, что выставленный номер не будет уникальным в течение периода.

По аналогичным причинам то же самое относится к долгопериодическим генераторам, таким как Mersenne Twister.

Ответ 14

есть несколько способов сделать это, чтобы построить массив с номерами 0000000 - 9999999, а затем выбрать случайный выбор этих чисел в этом массиве и поменяйте выбранные значения чисел с максимальным значением Max затем уменьшите max на 1 и выберите другой случайный элемент этого массива до нового максимума

каждый раз, уменьшая Max на один

например (в основном): (справа - комментарии, которые следует удалить в реальной программе) Rndfunc - это вызов любой функции генератора случайных чисел, которую вы используете

dim array(0 to 9999999) as integer
for x% = 1 to 9999999
array(x%)=x%
next x%
maxPlus = 10000000
max =9999999
pickedrandom =int(Rndfunc*maxPlus)  picks a random indext of the array based on    
                                   how many numbers are left
maxplus = maxplus-1
swap array(pickedrandom) , array(max) swap this array value to the current end of the
                                     array 
max = max -1                   decrement the pointer of the max array value so it 
                              points to the next lowest place..

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

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

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

Ответ 15

У PHP уже есть функция для этого, uniqid. Он генерирует стандартный uuid, который является большим, если вам нужно получить доступ к данным из других источников. Не изобретайте велосипед.

Ответ 16

Я, вероятно, не понял вашу точку зрения, но как насчет auto_increments?

Ответ 17

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

Основная идея состоит в том, что следующая формула seed * seed & p будет производить неповторяющиеся случайные числа для любого входа x such that 2x < p и p - x * x % p выдает все остальные случайные числа, не повторяющиеся, но только если p = 3 mod 4. Таким образом, в основном все, что вам нужно, - это единственный пример, близкий к 9999999, насколько это возможно. Таким образом, усилие может быть сведено к одному полю чтения, но с недостатком, который генерирует либо слишком большие идентификаторы, либо генерируется слишком мало идентификаторов.

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