Подтвердить что ты не робот

Эффективный метод поиска случайных чисел случайных чисел

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

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

Наивный код:

<?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, подход с логарифмическим диапазоном
  • если это не так, используйте чистый случайный метод

Что будет X и почему?

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

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

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

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

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

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

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

ВАЖНАЯ ИНФОРМАЦИЯ:

Я просто подумал о диапазоне/логарифмическом решении. Кажется, это полная фигня, извините за мою формулировку, потому что:

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

$id_k = transpose($id);

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

$id = transpose($id_k);

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

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

Ответ 3

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

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

Ответ 4

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

 ddddd-(rrr+n)

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

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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