Дякую всім за всі ваші чудові відповіді. Я закінчив таке рішення, яким я хотів би поділитися.
Перш ніж розібратися в деталях про те, про що і як, ось ось tl; dr : мій новий блискучий сценарій :-)
#!/usr/bin/env bash
#
# Generates a random integer in a given range
# computes the ceiling of log2
# i.e., for parameter x returns the lowest integer l such that 2**l >= x
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
# uses $RANDOM to generate an n-bit random bitstring uniformly at random
# (if we assume $RANDOM is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 60 bits
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
# alternative implementation of get_n_rand_bits:
# uses /dev/urandom to generate an n-bit random bitstring uniformly at random
# (if we assume /dev/urandom is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 56 bits
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
# for parameter max, generates an integer in the range {0..max} uniformly at random
# max can be an arbitrary integer, needs not be a power of 2
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
# MAIN SCRIPT
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
# need absolute value of diff since min (and also max) may be negative
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Збережіть це, ~/bin/randі у вас є доступна солодка випадкова функція в bash, яка може вибирати ціле число в заданому довільному діапазоні. Діапазон може містити від’ємні та додатні цілі числа та може бути до 2 60 -1 у довжину:
$ rand
Usage: rand [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
$ rand 1 10
9
$ rand -43543 -124
-15757
$ rand -3 3
1
$ for i in {0..9}; do rand $((2**60-1)); done
777148045699177620
456074454250332606
95080022501817128
993412753202315192
527158971491831964
336543936737015986
1034537273675883580
127413814010621078
758532158881427336
924637728863691573
Усі ідеї інших відповідачів були чудовими. Відповіді тердона , Дж. Ф. Себастьяна та Джімія використовували зовнішні інструменти, щоб виконати завдання просто та ефективно. Однак я віддав перевагу справжньому баш-рішенню для максимальної портативності, а може, і трохи, просто з любові до bash;)
Відповіді Рамеша та l0b0 використовуються /dev/urandomабо /dev/randomв поєднанні з od. Це добре, однак, їхні підходи мали недолік лише в тому, що вони могли вибирати випадкові цілі числа в діапазоні від 0 до 2 8n -1 для деякого n, оскільки цей метод вибірки байтів, тобто біт-рядки довжиною 8. Це досить великі стрибки з збільшуючи n.
Нарешті, відповідь Фалько описує загальну думку про те, як це можна зробити для довільних діапазонів (не тільки потужностей двох). В основному, для заданого діапазону {0..max}ми можемо визначити, яка наступна потужність двох, тобто, скільки саме бітів потрібно представити maxяк бітструмент. Тоді ми можемо відібрати лише стільки бітів і побачити, чи більша ця бістринга як ціле число max. Якщо так, повторіть. Оскільки ми відбираємо стільки бітів, скільки потрібно для представлення max, кожна ітерація має ймовірність, що більша або дорівнює 50% успіху (50% у гіршому випадку, 100% у кращому випадку). Тож це дуже ефективно.
Мій сценарій - це в основному конкретна реалізація відповіді Фалько, написаного в чистому стилі та високоефективний, оскільки він використовує вбудовані побітові операції bash для вибірки бітстригів потрібної довжини. Крім того, він шанує ідею Елія Кагана, яка пропонує використовувати вбудовану $RANDOMзмінну шляхом зменшення бітстрингу, що виникає внаслідок повторних викликів $RANDOM. Я реально реалізував і можливості використання, /dev/urandomі $RANDOM. За замовчуванням вищевказаний сценарій використовує $RANDOM. (І добре, якщо для використання /dev/urandomнам потрібні od і tr , але вони підтримуються POSIX.)
То як це працює?
Перш ніж я зайнятися цим, два спостереження:
Виявляється, bash не може обробляти цілі числа, більші за 2 63 -1. Побачте самі:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
Здається, що bash внутрішньо використовує підписані 64-бітні цілі числа для зберігання цілих чисел. Отже, у 2 63 воно «обертається» і ми отримуємо від’ємне ціле число. Тож ми не можемо сподіватися отримати будь-який діапазон більше 2 63 -1 із будь-якою випадковою функцією. Bash просто не впорається з цим.
Щоразу, коли ми хочемо відібрати значення в довільному діапазоні між 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. Це досить зрозуміло:
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
Умова нам потрібна, n>0тому якщо вона зростає занадто великою, загортається і стає негативною, цикл гарантовано припиняється.
Крок 2: Вибірка вибіркової довжини шматочка довжини n
Найбільш портативні ідеї - або використовувати /dev/urandom(або навіть /dev/randomякщо є вагомі причини), або вбудовувати $RANDOMзмінну bash . Давайте розглянемо, як це зробити $RANDOMспочатку.
Варіант A: Використання $RANDOM
Для цього використовується ідея, згадана Елією Каган. В основному, оскільки $RANDOMвибірки є 15-бітним цілим числом, ми можемо використовувати $((RANDOM<<15|RANDOM))для вибірки 30-бітного цілого числа. Це означає, що змініть перший виклик $RANDOMна 15 біт ліворуч і застосуйте побіжно або з другим викликом $RANDOM, ефективно змикаючи два незалежно відібраних бітстринга (або, принаймні, настільки ж незалежні, як йде вбудована версія bash $RANDOM).
Ми можемо повторити це, щоб отримати 45-бітне або 60-бітове ціле число. Після цього баш вже не може впоратися з цим, але це означає, що ми можемо легко відібрати випадкове значення між 0 і 2 60 -1. Отже, для вибірки n-бітового цілого числа ми повторюємо процедуру, поки наш випадковий біт-рядок, довжина якого зростає кроками 15 біт, не має довжини, більшої або дорівнює n. Нарешті, ми вирізаємо занадто багато бітів шляхом відповідного побітного зміщення вправо і закінчуємо n-бітним випадковим цілим числом.
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
Варіант В: Використання /dev/urandom
Крім того, ми можемо використовувати odі /dev/urandomвибірку n-бітового цілого числа. odбуде читати байти, тобто розрядність довжини 8. Аналогічно, як і в попередньому методі, ми відбираємо стільки байтів, що еквівалентна кількість вибірених бітів більша або дорівнює, і відрізаємо біти, які занадто багато.
Найменша кількість байтів, необхідна для отримання принаймні n біт, - найменше кратне значення 8, яке більше або рівне n, тобто підлогу ((n + 7) / 8).
Це працює лише до 56-бітових цілих чисел. Вибірка ще одного байта отримає нам 64-бітове ціле число, тобто значення до 2 64 -1, з яким bash не може впоратися.
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
Складання частин разом: Отримайте випадкові цілі числа у довільних діапазонах
nЗараз ми можемо відібрати біт-рядки, але ми хочемо відібрати цілі числа в діапазоні від 0до max, рівномірно , рівномірно , де maxможе бути довільне, а не обов'язково два. (Ми не можемо використовувати модуль, оскільки це створює зміщення.)
Вся суть, чому ми так наполегливо намагалися відібрати стільки бітів, скільки потрібно для представлення значення max, - це те, що тепер ми можемо безпечно (і ефективно) використовувати цикл для повторної вибірки nбітового біта, поки не відіб'ємо значення, яке є нижчим. або дорівнює max. У гіршому випадку ( maxце потужність у два), кожна ітерація закінчується з вірогідністю 50%, а в кращому випадку ( maxце сила двох мінус одна), перша ітерація закінчується напевно.
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
Згортання речей
Нарешті, ми хочемо вибірки цілих чисел між minі max, де minі maxможуть бути довільними, навіть від’ємними. Як було сказано раніше, це зараз банально.
Давайте покладемо все це на баш сценарій. Зробіть деякі матеріали для аналізу аргументів ... Ми хочемо два аргументи minта max, або лише один аргумент max, де minза замовчуванням 0.
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
... і, нарешті, для вибіркової вибіркової вибірки значення між minі max, ми вибираємо випадкове ціле число між 0і абсолютним значенням max-minта додаємо minдо кінцевого результату. :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Натхненний цим , я можу спробувати використати dieharder для тестування та порівняння цього PRNG, і розміщую свої висновки тут. :-)