Эффективный метод поиска случайных чисел без столкновений - PullRequest
18 голосов
/ 24 июля 2011

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

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

Наивный код:

<?php
    $found = false;
    while(!$found) {
      $uid = rand(1000000000,4294967295) // find random number betwen minimum and maximum
      $dbh->beginTransaction();
      // check if user id is in use, and if not insert it
      if($dbh->query("SELECT * FROM users WHERE uid = $uid")) {
        $dbh->exec("INSERT INTO users (uid) VALUES ($uid)");
        $found = true;
      }
      $dbh->commit();
    }
    // we just got our new uid ...
?>

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

Пример моей заботы:

  • 60% всех идентификаторов пользователей используются
  • Вероятность попадания в неиспользованный UID равна 0,4
  • первая попытка имеет 0,4% успеха
  • , если 1-ая неудача, вторая попытка имеет вероятность 0.6 * 0.4
  • так что с максимумом двух попыток у меня 0,4 + 0,6 * 0,4 вероятности (это правда ??)

Итак, один из способов оптимизации заключается в следующем:

  • найти случайное число, проверить, свободно ли оно, если нет, увеличить его на 1, повторить попытку и т. Д.
  • если выбрано максимальное число, продолжите с минимального числа

Это должно дать мне число с максимальным временем выполнения O (диапазон)

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

Я думаю, что это будет работать нормально, но я хочу, чтобы ЛУЧШЕ

Так что насчет этого?

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

Если я правильно подумаю, это даст ма число с максимальным временем O (log (range)).

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

Так что в начале наш чисто случайный метод, вероятно, лучше.

Так что насчет такого лимита

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

Что бы Х был и почему?

Итак, последний вопрос:

Это довольно легко и довольно сложно одновременно.

Я думаю, что это стандартная проблема, потому что многие системы используют случайные идентификаторы (заявки поддержки и т. Д.), Поэтому я не могу представить, что я первый, кто наткнулся на это.

Как бы вы решили это? Любой вклад оценивается!

Существует ли maby существующий класс / процедура, которую я могу использовать?

Или, может быть, некоторые функции базы данных, которые я могу использовать?

Я хотел бы сделать это в PHP / Mysql

ВАЖНОЕ РЕДАКТИРОВАНИЕ:

Я просто подумал о диапазоне / логарифмическом решении. Вроде бы полная фигня извините за мою формулировку потому что:

  • что если я нажму занятый номер при запуске?

Тогда я делю свой диапазон так долго, если он только 1. И даже тогда число занято.

Так что это абсолютно то же самое, что чисто случайный метод с самого начала, только хуже ...

Я немного смущен, что придумал это, но я оставлю это, потому что я думаю, что это хороший пример чрезмерно сложного мышления!

Ответы [ 8 ]

12 голосов
/ 24 июля 2011

Если p - это доля используемых идентификаторов, ваше «наивное» решение в среднем потребует 1 / (1-p) попыток найти неиспользуемый идентификатор. (См. Экспоненциальное распределение ). В случае 60% -ной занятости это всего лишь 1 / 0,4 = 2,5 запроса ...

Ваше "улучшенное" решение требует вызовов базы данных log (n), где n - количество используемых идентификаторов. Это немного больше, чем «наивное» решение. Кроме того, ваше улучшенное решение является неполным (например, оно не обрабатывает случай, когда берутся все числа в поддиапазоне, и не детализирует с поддиапазоном, в который вы возвращаетесь) и является более сложным для реализации для загрузки.

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

2 голосов
/ 24 июля 2011

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

 ddddd-(rrr+n)

ddddd - это, например, количество дней, в течение которых ваша система работала, rrr - случайное число, выбранное каждый день, n - приращение в течение дня.

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

2 голосов
/ 24 июля 2011

Джо, просто реализуй свой алгоритм, как указано выше, не беспокоясь.Просто посмотрите: если вероятность попадания в используемый идентификатор равна p = 0,6, то вероятность того, что вы нажмете используемый идентификатор N раз подряд, равна p ^ N.Это идет вниз по экспоненте!Я бы порекомендовал установить плотность ID ниже, например, до p = 0,1. Тогда вероятность того, что вы не добьетесь успеха в течение 10 последовательных попыток, равна p ^ 10 = 0,1 ^ 10 = 1e-10 !!!Абсолютно пренебрежимо мал.

Не беспокойтесь о столкновениях и найдите решение.

2 голосов
/ 24 июля 2011

Если вы не хотите проверять использованные числа, вы можете создать функцию, которая вычисляет случайный идентификатор $id_k на основе автоматически увеличивающегося идентификатора из базы данных $id:

$id_k = transpose($id);

Эта функция имеет либо двоюродного брата, либо может прозрачно транспонировать обратно (в идеале):

$id = transpose($id_k);

Затем вы можете использовать транспонированные идентификаторы на вашем сайте.

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

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

Вы можете использовать шифрование симметричным ключом (например, AES ) для шифрования счетчика. Если вы используете весь вывод (128 бит для AES), то вам гарантировано отсутствие коллизий, и это обратимое отображение.

128 битов может быть больше, чем вы хотите иметь в виду - это 32-значный шестнадцатеричный номер, 39-значный десятичный номер. Вы можете использовать 64-битный алгоритм шифрования, такой как DES , Blowfish или Misty (шестнадцатеричное 16-значное число, десятичное число из 20 цифр). *

1 голос
/ 25 июля 2011

Чтобы подробнее узнать о меритонах и Томаса Теленского , если вы хотите, чтобы ваши идентификаторы пользователя были короткими, при этом вы не исчерпали их, вы можете выбрать каждыйИдентификатор случайным образом, скажем, в диапазоне от 1 до 10 * n + 1000, где n - текущее количество пользователей, которое у вас есть.

Таким образом, ваше эффективноепространство идентификаторов пользователей никогда не будет заполнено более чем на 10%, в то время как идентификаторы пользователей (в долгосрочной перспективе) будут на одну цифру длиннее, чем если бы вы назначали их последовательно.Недостатком, конечно, является то, что идентификаторы больше не будут полностью коррелировать с порядком регистрации: если у кого-то есть идентификатор 5851, вы знаете, что он должен быть как минимум 486-м зарегистрированным пользователем, и что они вряд ли будут, скажем,50000th.(Конечно, вы вводите тот же тип корреляций, если когда-либо вручную настраиваете диапазон, чтобы учесть больше пользователей.)

Конечно, вы можете настроить константы 10 и 1000 выше: чем они больше, тем длиннее иболее случайными будут ваши идентификаторы пользователей.

1 голос
/ 24 июля 2011

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

1 голос
/ 24 июля 2011

Как насчет того, чтобы при запуске приложения выбрать случайный диапазон из 100 чисел (например, 100 - 199, 1000 - 1099, 5400 - 5499), проверить первое, если его нет в известной нам базе данных (основываясь на этомалгоритм), что все 100 бесплатны.Сохраните начало этого диапазона в памяти.

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

Это похоже на подход Nhibernate hi / lo (за исключением случайного бита).

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

...