Алгоритм формирования случайного числа
Я ищу, чтобы сгенерировать случайное число и выдать его в таблицу в базе данных для конкретного user_id. Загвоздка в том, что одно и то же число нельзя использовать дважды. Существует миллион способов сделать это, но я надеюсь, что кто-то очень увлечен алгоритмами, имеет умный способ решения проблемы в элегантном решении, в котором выполняются следующие критерии:
1) выполняется наименьшее количество запросов к базе данных. 2) наименьшее количество обхода структуры данных в памяти сделанный.
по существу идея состоит в том, чтобы сделать следующее
1) Создайте случайное число от 0 до 9999999
2) Проверьте базу данных, чтобы увидеть, если номер существует
Или
2) запросить базу данных для всех номеров
3) посмотрите, соответствует ли возвращенный результат тому, что пришло из db
4) если он соответствует, повторите шаг 1, если нет, проблема решена.
спасибо.
17 ответов
нет, ваш алгоритм не является масштабируемым. То, что я делал раньше, - это последовательно выдавать числа (+1 каждый раз), а затем передавать их через операцию XOR, чтобы перемешать биты, давая мне, казалось бы, случайные числа. Конечно, они не являются случайными, но они выглядят так для глаз пользователей.
[Edit] дополнительная информация
логика этого алгоритма выглядит так: вы используете известную последовательность для генерировать уникальные числа, а затем детерминировать манипулировать ими, так что они больше не смотреть сериал. Общее решение-использовать какая-то форма шифрования, которая в моем случае была XOR flipflop, потому что его так быстро, как он может получить, и он выполняет гарантию, что номера никогда не столкнется.
однако вы можете использовать другие формы шифрования, если хотите, предпочитаете еще больше случайные числа, превышающие скорость (скажем, вам не нужно генерировать много ids за раз). Теперь важный момент в выборе алгоритма шифрования это " гарантирую, что числа никогда не столкнутся". И способ доказать, может ли алгоритм шифрования выполнить эта гарантия должна проверить, если исходное число и результат шифрование имеет такое же количество битов, и что алгоритм обратимый (биекция).
[спасибо Адам Лисс & CesarB для exapanding на решении]
Почему бы вам просто не использовать GUID? Большинство языков должны иметь встроенный способ сделать это. Он гарантированно будет уникальным (с очень разумными границами).
хотите более-топ решение?
Я предполагаю, что случайность не предназначена для качества шифрования, но достаточно, чтобы препятствовать угадыванию долговечности пользователя user_id.
во время разработки создайте список всех 10 миллионов чисел в Строковой форме.
при необходимости выполните простое преобразование, например, добавьте постоянную строку в середину. (Это на случай, если результат окажется слишком предсказуемым.)
передайте их в инструмент это порождает идеальной хэш-функции, например gperf.
полученный код можно использовать для быстрого кодирования идентификатора пользователя во время выполнения в уникальное хэш-значение, которое гарантированно не конфликтует с любыми другими хэш-значениями.
предположим:
- рандоме нужен для уникальности, а не для безопасности!--4-->
- Ваш идентификатор пользователя 32 бит
- ваш предел 9999999 был просто пример
вы можете сделать что-то простое, как иметь случайное число как 64-битное целое число, с верхними 32 битами, содержащими метку времени (при вставке строки), а нижние 32 бита user_id. Это было бы уникально даже для нескольких строк с одним и тем же пользователем, если вы используете соответствующее разрешение на метку в зависимости от того, как часто вы добавляете новые строки для одного пользователя. Объедините с уникальным ограничением на случайный столбец и поймайте любую такую ошибку в своей логике, а затем просто повторите попытку.
Я думаю, вы найдете, что вы действительно не хотите этого делать. По мере увеличения числа в базе данных вы можете потратить слишком много времени на цикл "убедитесь, что это число не занято".
лично мне повезло с хэшами в качестве альтернативы, но, чтобы придумать лучшее решение, я действительно хочу знать, почему вы хотите сделать это таким образом.
мой опыт просто использовал RNG в PHP. Я обнаружил, что используя определенный размер числа (я использую int, поэтому у меня есть максимум 4G). Я провел несколько тестов и обнаружил, что в среднем за 500 000 итераций я получил 120 одиночных дубликатов. Я никогда не получал трипликат после запуска цикла несколько раз. Мое "решение" состояло в том, чтобы просто вставить и проверить, если это не удастся, а затем сгенерировать новый идентификатор и пойти снова.
мой совет сделать то же самое и посмотреть, что ваша скорость столкновения &c и посмотрите, приемлемо ли это для вашего дела.
Это не оптимально, поэтому, если у кого-то есть предложения, я тоже смотрю:)
EDIT: я был ограничен 5-значным идентификатором ([a-zA-z0-9]{5,5}), тем дольше идентификатор (больше комбинаций, несколько столкновений). Например, md5 электронной почты почти никогда не конфликтует.
проблема в том, что если вы генерируете случайные числа, очень возможно производить дубликаты бесконечно.
однако:
<?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;
}
}
?>
в то время как это будет делать то, что вы хотите, это плохая идея, так как это не будет масштабироваться долго, в конечном итоге Ваш массив станет большим, и потребуется очень много времени, чтобы создать случайный, который еще не находится в вашей БД.
легко разработать генератор псевдослучайных чисел с большим периодом неповторения; например,этот, который используется для того же, для чего вы его хотите.
кстати, почему бы просто не выпустить userid последовательно?
Мне нравится идея Oddthinking, но вместо того, чтобы выбирать самую сильную хэш-функцию в мире, вы можете просто:
- генерировать MD5 из первых 10 миллионов чисел (выраженных в виде строк, +некоторая соль)
- проверка дубликатов offline, т. е. перед выходом в производство (думаю, их не будет)
- хранить дубликаты в массиве где-нибудь
- при запуске приложения, загрузить массив
- когда вы хотите вставить идентификатор, выберите следующий номер, вычислите его MD5, проверьте, находится ли он в массиве, и если он не используется в качестве идентификатора в базе данных. В противном случае выберите следующий номер
MD5 быстры,и проверка, принадлежит ли строка массиву, позволит вам избежать выбора.
Если вы действительно хотите получить "случайные" числа от 0 до 9 999 999, то решение состоит в том, чтобы сделать "рандомизацию" один раз, а затем сохранить результат на свой диск.
нетрудно получить желаемый результат, но я думаю, что это больше похоже на "сделать длинный список с числами", чем "получить случайное число".
$array = range(0, 9999999);
$numbers = shuffle($array);
вам также нужен указатель на текущую позицию в $numbers (сохраните его в базе данных); начните с 0 и увеличивайте его каждый раз, когда вам нужно новое число. (Или ты можно использовать array_shift () или array_pop (), если вам не нравится использовать указатели.)
правильный алгоритм PRNG (генератор псевдослучайных чисел) будет иметь время цикла, в течение которого он никогда не будет в том же состоянии. Если вы выставите все состояние PRNG в количестве, полученном из него, вы получите число, гарантированное уникальным для периода генератора.
простой PRNG, который делает это, называется'Линейный Конгруэнтный ' PRNG, который повторяет формулу:
X(i) = AX(i-1)|M
используя правильную пару факторов, вы можете получить период 2^30 (приблизительно 1 миллиард) от простого PRNG с 32-битным аккумулятором. Обратите внимание, что вам понадобится 64-битная длинная временная переменная для хранения промежуточной части вычисления "AX". Большинство, если не все компиляторы C не поддерживает этот тип данных. Вы также должны иметь возможность делать это с числовым типом данных на большинстве диалектов SQL.
при правильных значениях A и M мы можем получить генератор случайных чисел с хорошими статистическими и геометрическими свойствами. Есть известная газета об этом написали Фишман и Мур.
для M = 2^31-1 мы можем использовать значения ниже, чтобы получить PRNG с хорошим длительным периодом (2^30 IIRC).
хорошие значения A:
742,938,285
950,706,376
1,226,874,159
62,089,911
1,343,714,438
обратите внимание, что этот тип генератора (по определению) не является криптографически безопасным. Если вы знаете последнее число, сгенерированное из него, вы можете предсказать, что он будет делать дальше. К сожалению, я считаю, что вы не можете получить криптографическую безопасность и гарантированную неповторяемость на в то же время. Чтобы PRNG был криптографически безопасным (например,Блюм Блюм Шуб) он не может предоставить достаточное состояние в сгенерированном числе, чтобы можно было предсказать следующее число в последовательности. Поэтому внутреннее состояние шире, чем сгенерированное число, и (чтобы иметь хорошую безопасность) период будет больше, чем количество возможных значений, которые могут быть сгенерированы. Это означает, что выставленное число не будет уникальным в течение периода.
для аналогичные причины то же самое верно для длиннопериодных генераторов, таких как Мерсенн Твистер.
Я на самом деле ранее писал статьи об этом. Он использует тот же подход, что и ответ Роберта Гулда, но дополнительно показывает, как сократить блочный шифр до подходящей длины с помощью складывания xor, а затем как генерировать перестановки в диапазоне, который не является степенью 2, сохраняя при этом свойство уникальности.
есть несколько способов сделать это одним из способов было бы построить массив с номерами 0000000 через 9999999, а затем выбрать случайный выбор этих чисел в этом массиве и поменять выбранные значения чисел с максимальным значением Max затем уменьшите max на 1 и выберите другой случайный член этого массива до нового максимума
каждый раз, уменьшая Max на один
например (в basic) : (справа находятся комментарии, которые должны быть удалены в фактическом программа) 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..
тогда продолжайте делать это для каждого числа, которое вы хотите выбрать, но вам нужно будет иметь возможность использовать очень большие массивы
другой метод будет следующим :сгенерируйте число и сохраните его в массив, который может динамически расти затем после этого выберите новое число и сравните его со значением, которое находится на полпути от первого до последнего элемента массива в этом случае это будет первый выбранный номер если он соответствует выбрать другое случайное число, сортировать массив по размеру, и если нет совпадения, то в зависимости от погоды он больше или меньше, чем число, которое вы сравнивали его с вами идти вверх или вниз в списке половину половины расстояния, каждый раз, когда он не соответствует и больше или меньше, чем то, что вы сравниваете его с.
каждый раз, разделяя его пополам, пока вы не достигнете размера зазора одного, затем вы проверяете один раз и останавливаетесь, поскольку нет матч, а затем номер добавляется в список и список перестановки в порядке возрастания, так далее и так далее, пока вы не закончите сбор случайных чисел... надеюсь, это поможет..
PHP уже имеет функцию для этого,uniqid. Он генерирует стандартный uuid, который отлично подходит, если вам нужно получить доступ к данным из другого места. Не изобретайте колесо.
если вы хотите убедиться, что случайные числа не повторяются, вам нужен неповторяющийся генератор случайных чисел (как описано здесь).
основная идея заключается в том, что следующей формуле seed * seed & p
будет производиться неповторяющиеся случайные числа для любого входа x such that 2x < p
и p - x * x % p
производит все другие случайные числа, а также неповторяющиеся, но только если p = 3 mod 4
. Так что в основном все, что вам нужно, это один primnumber как можно ближе к 9999999
как это возможно. Таким образом, усилие может быть сведено к одному полю чтения, но с недостатком, что либо генерируются слишком большие идентификаторы, либо генерируется слишком мало идентификаторов.
этот алгоритм не очень хорошо переставляется, поэтому я бы рекомендовал объединить его с XOR или добавлением или каким-либо другим подходом для изменения точного значения без разрушения отношения 1 к 1 между семенами и их сгенерированным значением.