Мне было интересно, что было бы лучшим способом получить хорошую случайность в bash, то есть, что было бы процедурой для получения случайного положительного целого числа между MIN
и MAX
таким, что
- Диапазон может быть сколь угодно большим (или, по крайней мере, скажем, до 2 32 с -1);
- Значения распределены равномерно (т. Е. Нет смещения);
- Это эффективно.
Эффективный способ получить случайность в bash - использовать $RANDOM
переменную. Однако это только выборка значения между 0 и 2 15 -1, которое может быть недостаточно большим для всех целей. Люди обычно используют по модулю, чтобы получить его в диапазоне, который они хотят, например,
MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))
Это, кроме того, создает смещение, если только не $MAX
произойдет деление 2 15 -1 = 32767. Например, если $MIN
0 и $MAX
9, то значения от 0 до 7 немного более вероятны, чем значения 8 и 9, поскольку $RANDOM
никогда не будут 32768 или 32769. Это смещение ухудшается с увеличением диапазона, например, если $MIN
0 и $MAX
равно 9999, а затем цифры от 0 до 2767 имеют вероятность 4 / 32767 , в то время как число 2768 до 9999 есть только вероятность 3 / 32767 .
Таким образом, хотя вышеуказанный метод удовлетворяет условию 3, он не удовлетворяет условиям 1 и 2.
Наилучший метод, который я до сих пор придумывал, пытаясь удовлетворить условия 1 и 2, заключался /dev/urandom
в следующем:
MIN=0
MAX=1234567890
while
rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
[ -z $rnd ] && rnd=0
(( $rnd < $MIN || $rnd > $MAX ))
do :
done
В основном, просто собирать случайность из /dev/urandom
(можно рассмотреть использование /dev/random
вместо этого, если требуется криптографически сильный генератор псевдослучайных чисел, и если у вас много времени, или, может быть, аппаратный генератор случайных чисел), удалите каждый символ, который не является десятичной цифрой, сложите выводим на длину $MAX
и режем ведущие 0. Если нам довелось получить только 0, то $rnd
пусто, поэтому в этом случае установите rnd
значение 0
. Проверьте, находится ли результат за пределами нашего диапазона, и если да, то повторите. Я ввел здесь «тело» цикла while в защитное устройство, чтобы вызвать выполнение тела хотя бы один раз, в духе эмуляции do ... while
цикла, поскольку rnd
для начала не определено.
Я думаю, что я выполнил условия 1 и 2 здесь, но теперь я испортил условие 3. Это довольно медленно. Занимает до секунды или около того (десятой секунды, когда мне повезет). На самом деле, цикл даже не гарантированно завершается (хотя вероятность завершения сходится к 1 с увеличением времени).
Есть ли эффективный способ получить непредвзятые случайные целые числа, в заранее заданном и потенциально большом диапазоне, в bash? (Я продолжу исследовать, когда позволит время, но в то же время я подумал, что кто-то здесь может иметь классную идею!)
Таблица ответов
Самая основная (и, следовательно, переносимая) идея состоит в том, чтобы генерировать случайную цепочку битов достаточно долго. Существуют разные способы генерирования случайной цепочки битов, либо используя встроенную
$RANDOM
переменную bash, либо используяod
и/dev/urandom
(или/dev/random
). Если случайное число больше чем$MAX
, начните сначала.В качестве альтернативы, можно использовать внешние инструменты.
- Решение Perl
- Pro: довольно портативный, простой, гибкий
- Против: не для очень больших чисел выше 2 32 -1
- Решение Python
- Pro: просто, гибко, работает даже для большого количества
- Против: менее портативный
- Zsh решение
- Pro: хорошо для людей, которые используют Zsh в любом случае
- Против: вероятно, даже менее портативный
- Решение Perl
источник
rand=$(command)
сделать, еслиcommand
вернет итератор, который удовлетворяет вашим требованиям?dd if=/dev/urandom 2>/dev/null
и пропуская ееod -t d
(избегая обхода через base64), но мне не ясно, как происходит преобразование и действительно ли оно непредвзято. Если вы сможете расширить свою идею до эффективного, работающего сценария и объяснить, почему нет предвзятости, это послужит хорошим ответом. :)python
илиperl
или ваш любимый язык, но это не везде доступно. Я бы предпочел что-то более портативное. Ну,awk
случайная функция была бы в порядке, я думаю. Но чем более портативным, тем лучше :)perl -e 'print int(rand(2**32-1))');
. Это чертовски портативно и будет очень быстро. Awk не будет сокращать его, так как большинство реализаций начинаются с одного и того же семени. Таким образом, вы получаете то же случайное число при последующих запусках. Это только изменяется в пределах того же самого запуска.Ответы:
Я вижу другой интересный метод отсюда .
Этот также, кажется, хороший вариант. Он читает 4 байта из случайного устройства и форматирует их как целое число без знака между
0
и2^32-1
.источник
/dev/urandom
если вы не знаете, что вам нужно/dev/random
;/dev/random
блоки на линуксе.od
команды разные. Оба просто выводят 4-байтовые целые числа без знака: 1-й - из openssl, 2-й - из/dev/random
./dev/urandom
вместо/dev/random
- я не вижу смысла использовать/dev/random
, и это может быть очень дорогим / медленным или замедлять работу других частей системы. (Не стесняйтесь, отредактируйте назад и объясните, действительно ли это необходимо.)I
означает,sizeof(int)
что может быть меньше, чем4
в принципе. Кстати,od -DAn
не удается,(2**32-1)
ноod -N4 -tu4 -An
продолжает работать.Спасибо всем за все ваши отличные ответы. Я получил следующее решение, которым я хотел бы поделиться.
Прежде чем углубляться в подробности о том, почему и как, вот tl; dr : мой новый блестящий сценарий :-)
Сохраните это,
~/bin/rand
и в вашем распоряжении будет приятная случайная функция в bash, которая может выбирать целое число в заданном произвольном диапазоне. Диапазон может содержать отрицательные и положительные целые числа и может быть длиной до 2 60 -1:Все идеи других ответчиков были великолепны. В ответах Тердона , Дж. Ф. Себастьяна и Джиммия использовались внешние инструменты для простого и эффективного выполнения задачи. Тем не менее, я предпочел настоящее решение bash для максимальной переносимости, и, возможно, немного, просто из любви к bash;)
Ответы Рамеша и l0b0 используются
/dev/urandom
или/dev/random
в сочетании сod
. Это хорошо, однако, их подходы имели тот недостаток, что они могли выбирать случайные целые числа в диапазоне от 0 до 2 8n -1 для некоторого n, поскольку этот метод выбирает байты, то есть цепочки битов длины 8. Это довольно большие переходы с увеличивая п.Наконец, ответ Фалько описывает общую идею, как это можно сделать для произвольных диапазонов (не только степеней двойки). По сути, для данного диапазона
{0..max}
мы можем определить, какова следующая степень двух, т. Е. Сколько именно битов требуется представитьmax
в виде цепочки битов. Затем мы можем сэмплировать столько битов и посмотреть, больше ли это число в виде целого числаmax
. Если так, повторите. Поскольку мы выбираем столько битов, сколько требуется для представленияmax
, каждая итерация имеет вероятность, превышающую или равную 50% успеха (50% в худшем случае, 100% в лучшем случае). Так что это очень эффективно.Мой сценарий, в основном, представляет собой конкретную реализацию ответа Falco, написанную на чистом bash и очень эффективную, поскольку он использует встроенные побитовые операции bash для выборки цепочек битов желаемой длины. Кроме того, она поддерживает идею Элии Кагана, которая предлагает использовать встроенную
$RANDOM
переменную путем объединения цепочек битов, возникающих в результате повторных вызовов$RANDOM
. Я фактически реализовал обе возможности/dev/urandom
и$RANDOM
. По умолчанию вышеуказанный скрипт использует$RANDOM
. (И хорошо, если/dev/urandom
мы используем od и tr , но они поддерживаются POSIX.)Итак, как это работает?
Прежде чем я углублюсь в это, два замечания:
Оказывается, bash не может обрабатывать целые числа больше 2 63 -1. Посмотреть на себя:
Казалось бы, bash внутренне использует 64-битные целые числа со знаком для хранения целых чисел. Таким образом, в 2 63 оно «оборачивается», и мы получаем отрицательное целое число. Поэтому мы не можем надеяться получить какой-либо диапазон больше, чем 2 63 с какой-либо случайной функцией, которую мы используем Баш просто не может с этим справиться.
Всякий раз, когда мы хотим выбрать значение в произвольном диапазоне между
min
иmax
с возможноmin != 0
, мы можем просто выбрать значение между0
иmax-min
вместо, а затем добавитьmin
к конечному результату. Это работает , даже еслиmin
и возможно , такжеmax
являются отрицательными , но мы должны быть осторожны , чтобы попробовать значение между0
и абсолютное значениеmax-min
. Итак, мы можем сосредоточиться на том, как выбрать случайное значение между0
произвольным положительным целым числомmax
. Остальное легко.Шаг 1: Определите, сколько битов необходимо для представления целого числа (логарифм)
Таким образом, для данного значения
max
мы хотим знать, сколько бит необходимо, чтобы представить его как цепочку битов. Это сделано для того, чтобы в дальнейшем мы могли произвольно выбирать столько битов, сколько необходимо, что делает скрипт таким эффективным.Посмотрим. Поскольку с
n
битами мы можем представить до значения 2 n -1, то числоn
битов, необходимое для представления произвольного значения,x
является максимальным (log 2 (x + 1)). Итак, нам нужна функция для вычисления потолка логарифма к основанию 2. Это довольно очевидно:Нам нужно условие,
n>0
поэтому, если оно слишком велико, оборачивается и становится отрицательным, цикл гарантированно завершится.Шаг 2: выборка случайной строки длины
n
Наиболее переносимые идеи - использовать
/dev/urandom
(или даже/dev/random
если есть веские причины) встроенную$RANDOM
переменную bash . Давайте$RANDOM
сначала посмотрим, как это сделать .Вариант А: Использование
$RANDOM
Здесь используется идея, упомянутая Элией Каганом. По сути, поскольку выборка
$RANDOM
15-разрядного целого числа, мы можем использовать$((RANDOM<<15|RANDOM))
для выборки 30-разрядного целого числа. Это означает, что сдвиньте первый вызов$RANDOM
на 15 бит влево и примените побитовый или со вторым вызовом$RANDOM
, эффективно объединяя две независимо выбранные цепочки битов (или, по крайней мере, столь же независимые, как встроенная команда bash$RANDOM
).Мы можем повторить это, чтобы получить 45-битное или 60-битное целое число. После этого bash больше не может это обрабатывать, но это означает, что мы можем легко выбрать случайное значение между 0 и 2 60 -1. Итак, для выборки n-битного целого числа мы повторяем процедуру до тех пор, пока наша случайная цепочка битов, длина которой увеличивается с шагом 15 бит, не будет иметь длину, большую или равную n. Наконец, мы обрезаем слишком много битов путем соответствующего побитового сдвига вправо, и в итоге получаем n-битное случайное целое число.
Вариант Б: Использование
/dev/urandom
В качестве альтернативы мы можем использовать
od
и/dev/urandom
для выборки n-битного целого числа.od
будет считывать байты, т. е. битовые строки длиной 8. Как и в предыдущем методе, мы выбираем столько байтов, что эквивалентное количество дискретных битов больше или равно n, и обрезаем слишком большие биты.Наименьшее количество байтов, необходимое для получения по меньшей мере n битов, является наименьшим кратным 8, которое больше или равно n, т. Е. Floor ((n + 7) / 8).
Это работает только до 56-битных целых чисел. Выборка еще одного байта дала бы нам 64-битное целое число, то есть значение до 2 64 с -1, которое bash не может обработать.
Соединение частей: получить случайные целые числа в произвольных диапазонах
Теперь мы можем сэмплировать
n
битовые строки -бит, но мы хотим сэмплировать целые числа в диапазоне от0
доmax
, равномерно случайным образом , гдеmax
может быть произвольным, а не обязательно степенью двойки. (Мы не можем использовать по модулю, поскольку это создает уклон.)Весь смысл, почему мы так старались собрать столько битов, сколько необходимо для представления значения
max
, заключается в том, что теперь мы можем безопасно (и эффективно) использовать цикл для многократной выборкиn
-битной цепочки битов, пока мы не выберем значение, которое меньше или равноmax
. В худшем случае (max
это степень двойки) каждая итерация заканчивается с вероятностью 50%, а в лучшем случае (max
это степень два минус один) первая итерация завершается с уверенностью.Завершение вещей
Наконец, мы хотим выбрать целые числа между
min
иmax
, гдеmin
иmax
может быть произвольным, даже отрицательным. Как упоминалось ранее, это теперь тривиально.Давайте поместим все это в сценарий bash. Делаем какие-то аргументы при разборе ... Нам нужны два аргумента
min
иmax
, или только один аргументmax
, где поmin
умолчанию0
.... и, наконец, для выборки случайным образом значения между
min
иmax
мы выбираем случайное целое число между0
и абсолютным значениемmax-min
и добавляемmin
к конечному результату. :-)Вдохновленный этим , я мог бы попытаться использовать dieharder для тестирования и тестирования этого PRNG и выложить свои результаты здесь. :-)
источник
sizeof(int) == 8
(64 бита) из-за--format=u
random.Random
класс использует 53бит? Генератор для возврата произвольных больших случайных чисел (несколько вызовов),random.SystemRandom
делает то же самое с помощью,os.urandom()
что может быть реализовано с помощью/dev/urandom
.--format=u8
то я жестко закодирую предположениеsizeof(int)==8
. С другой стороны, при использовании--format=uL
проблем нет: я не думаю, что есть платформа, которая имеет 64-битные целые числа, но все же определяет длинные целые как нечто более низкое. Так что в принципе я бы сказал, что это--format=uL
обеспечивает большую гибкость. о чем ты думаешь?long long
что может быть 64bit в то время как INT = длинный = 32bit на некоторых платформах. Вы не должны претендовать на диапазон 0..2 ** 60, если вы не можете гарантировать его на всех платформах. С другой стороны, bash может не поддерживать сам этот диапазон на таких платформах (я не знаю, возможно, он использует maxint_t, и тогда u8 более корректен, если вы хотите установить фиксированный диапазон (od
не поддерживает указание maxint, если ваш диапазон какой бы bash не зависел от платформы? range:). Если диапазон bash зависит от sizeof long, тогда uL может быть более подходящим). Вы хотите полный диапазон, который поддерживает bash на всех ОС или фиксированный диапазон?Это может быть Zsh?
Вы можете также использовать семена с
rand48(seed)
. Смотритеman zshmodules
иman 3 erand48
подробное описание, если интересно.источник
python
доступно в Redhat, системах на основе Debian.источник
Если вам нужно число от 0 до (2 ^ n) -1, где n mod 8 = 0, вы можете просто получить n / 8 байт
/dev/random
. Например, чтобы получить десятичное представление случайного числа,int
вы можете:Если вы хотите взять только n битов, вы можете сначала взять предельные (n / 8) байты и сдвинуть вправо на требуемую величину. Например, если вы хотите 15 бит:
Если вы абсолютно уверены, что вас не волнует качество случайности, и вы хотите гарантировать минимальное время выполнения, которое вы можете использовать
/dev/urandom
вместо/dev/random
. Убедитесь, что вы знаете, что делаете, прежде чем использовать/dev/urandom
!источник
n
случайные байты из/dev/urandom
и отформатировать, используяod
. По духу похоже на этот ответ . Оба одинаково хороши :) Хотя оба имеют недостаток: фиксированный диапазон от 0 до 2 ^ (n * 8) -1 бит, где n - количество байтов. Я бы предпочел метод для произвольного диапазона, до 2 ^ 32-1, но также и для любого более низкого. Это создает сложность смещения./dev/urandom
вместо/dev/random
- я не вижу смысла использовать/dev/random
, и это может быть очень дорогим / медленным или замедлять работу других частей системы. (Не стесняйтесь, отредактируйте назад и объясните, действительно ли это необходимо.)/dev/urandom
результаты намного хуже, чем/dev/random
то, что урандом в большинстве случаев неприменимо. Один раз/dev/urandom
инициализируется (при запуске системы); его результаты так же хороши, как и/dev/random
для почти всех приложений в Linux. В некоторых системах случайное и случайное совпадают.--format=u
следует заменить,--format=u4
потому чтоsizeof(int)
может быть меньше, чем4
в теории./dev/random
и/dev/urandom
являются неудовлетворительными, и что «Linux следует добавить безопасный ГСЧ , который блокирует до него накопилось достаточное семян энтропию , а затем ведет себя какurandom
.»Если вы не возражаете против использования внешних инструментов, это должно соответствовать вашим требованиям:
Он использует
rand
функцию Perl, которая принимает верхний предел в качестве параметра. Вы можете установить его на то, что вам нравится. То, насколько это близко к истинной случайности в абстрактном математическом определении, выходит за рамки этого сайта, но все должно быть хорошо, если вам не нужно это для чрезвычайно чувствительного шифрования или тому подобного. Возможно, даже там, но я не буду рисковать мнением.источник
1^32-1
но вам нужно настроить его для больших чисел.Вы должны получить ближайшее (2 ^ X) -1, равное или большее, чем желаемый максимум, и получить число битов. Затем просто вызовите / dev / random несколько раз и добавьте все биты вместе, пока у вас не будет достаточно, обрезая все биты, которых слишком много. Если полученное число больше вашего максимального повтора. В худшем случае у вас больше 50% шансов получить случайное число ниже вашего максимума, поэтому (в этом худшем случае) вы в среднем совершите два звонка.
источник
/dev/urandom
, но в обоих ответах это всегда кратна 8 бит. Обрезание битов, которые слишком велики для более низких диапазонов, перед форматированием до десятичного с,od
является хорошей идеей для повышения эффективности, поскольку, как вы хорошо объясните, цикл имеет только ожидаемое количество из 2 итераций. Это, в сочетании с одним из упомянутых ответов, вероятно, будет правильным решением.Ваш ответ интересный, но довольно длинный.
Если вы хотите произвольно большие числа, вы можете объединить несколько случайных чисел в помощник:
Если проблема в предвзятости, то просто удалите ее.
Соединение этих функций вместе
источник