|
Поиск по сайту:
Главная
О проекте
Web-мастеру
HTML & JavaScript
SSI
Perl
PHP
XML & XSLT
Unix Shell
MySQL
Безопасность
Хостинг
Другое
|
|
9.6. $RANDOM: генерация псевдослучайных целых чисел
$RANDOM -- внутренняя функция Bash (не константа), которая
возвращает псевдослучайные целые числа в
диапазоне 0 - 32767. Функция $RANDOM не должна использоваться для
генераци ключей шифрования.
Пример 9-23. Генерация случайных
чисел
#!/bin/bash
# $RANDOM возвращает различные случайные числа при каждом обращении к ней.
# Диапазон изменения: 0 - 32767 (16-битовое целое со знаком).
MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT случайных чисел:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ] # Генерация 10 ($MAXCOUNT) случайных чисел.
do
number=$RANDOM
echo $number
let "count += 1" # Нарастить счетчик.
done
echo "-----------------"
# Если вам нужны случайные числа не превышающие определенного числа,
# воспользуйтесь оператором деления по модулю (остаток от деления).
RANGE=500
echo
number=$RANDOM
let "number %= $RANGE"
echo "Случайное число меньше $RANGE --- $number"
echo
# Если вы желаете ограничить диапазон "снизу",
# то просто производите генерацию псевдослучайных чисел в цикле до тех пор,
# пока не получите число большее нижней границы.
FLOOR=200
number=0 # инициализация
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
done
echo "Случайное число, большее $FLOOR --- $number"
echo
# Эти два способа могут быть скомбинированы.
number=0 #initialize
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # Ограничение "сверху" числом $RANGE.
done
echo "Случайное число в диапазоне от $FLOOR до $RANGE --- $number"
echo
# Генерация случайных "true" и "false" значений.
BINARY=2
number=$RANDOM
T=1
let "number %= $BINARY"
# let "number >>= 14" дает более равномерное распределение
# (сдвиг вправо смещает старший бит на нулевую позицию, остальные биты обнуляются).
if [ "$number" -eq $T ]
then
echo "TRUE"
else
echo "FALSE"
fi
echo
# Можно имитировать бросание 2-х игровых кубиков.
SPOTS=7 # остаток от деления на 7 дает диапазон 0 - 6.
ZERO=0
die1=0
die2=0
# Кубики "выбрасываются" раздельно.
while [ "$die1" -eq $ZERO ] # Пока на "кубике" ноль.
do
let "die1 = $RANDOM % $SPOTS" # Имитировать бросок первого кубика.
done
while [ "$die2" -eq $ZERO ]
do
let "die2 = $RANDOM % $SPOTS" # Имитировать бросок второго кубика.
done
let "throw = $die1 + $die2"
echo "Результат броска кубиков = $throw"
echo
exit 0
Пример 9-24. Выбор случайной карты из
колоды
#!/bin/bash
# pick-card.sh
# Пример выбора случайного элемента массива.
# Выбор случайной карты из колоды.
Suites="Треф
Бубей
Червей
Пик"
Denominations="2
3
4
5
6
7
8
9
10
Валет
Дама
Король
Туз"
suite=($Suites) # Инициализация массивов.
denomination=($Denominations)
num_suites=${#suite[*]} # Количество элементов массивов.
num_denominations=${#denomination[*]}
echo -n "${denomination[$((RANDOM%num_denominations))]} "
echo ${suite[$((RANDOM%num_suites))]}
# $bozo sh pick-cards.sh
# Валет Треф
# Спасибо "jipe," за пояснения по работе с $RANDOM.
exit 0
|
Jipe подсказал еще один способ
генерации случайных чисел из заданного диапазона.
# Генерация случайных чисел в диапазоне 6 - 30.
rnumber=$((RANDOM%25+6))
# Генерируется случайное число из диапазона 6 - 30,
#+ но при этом число должно делиться на 3 без остатка.
rnumber=$(((RANDOM%30/3+1)*3))
# Примечательно, если $RANDOM возвращает 0
# то это приводит к возникновению ошибки.
# Упражнение: Попробуйте разобраться с выражением самостоятельно.
Bill
Gradwohl предложил усовершенствованную
формулу генерации положительных псевдослучайных чисел
в заданном диапазоне.
rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))
В сценарии ниже, Bill представил универсальную
функцию, которая возвращает псевдослучайное число из
заданного диапазона.
Пример 9-25. Псевдослучайное число из
заданного диапазона
#!/bin/bash
# random-between.sh
# Псевдослучайное число из заданного диапазона.
# Автор: Bill Gradwohl,
# незначительные изменения внесены автором документа.
# Используется с разрешения автора сценария.
randomBetween() {
# Генерация положительных и отрицательных псевдослучайных чисел,
#+ в диапазоне от $min до $max,
#+ которые кратны числу $divisibleBy.
#
# Bill Gradwohl - Oct 1, 2003
syntax() {
# Вложенная функция.
echo
echo "Порядок вызова: randomBetween [min] [max] [multiple]"
echo
echo "Функция ожидает до 3-х входных аргументов, но они не являются обязательными."
echo "min -- нижняя граница диапазона"
echo "max -- верхняя граница диапазона"
echo "multiple -- делитель, на который должен делиться результат без остатка."
echo
echo "Если какой либо из параметров отсутствует, по-умолчанию принимаются значения: 0 32767 1"
echo "В случае успеха функция возвращает 0, иначе -- 1"
echo "и это сообщение о порядке вызова."
echo "Результат возвращается в глобальной переменной randomBetweenAnswer"
echo "Входные параметры, имеющие отрицательные значения, обрабатываются корректно."
}
local min=${1:-0}
local max=${2:-32767}
local divisibleBy=${3:-1}
# При отсутствии какого либо из входных параметров, они принимают значения по-умолчанию.
local x
local spread
# Делитель должен быть положительным числом.
[ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
# Проверка корректности входных параметров.
if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
syntax
return 1
fi
# Если min больше чем max, то поменять их местами.
if [ ${min} -gt ${max} ]; then
# Поменять местами.
x=${min}
min=${max}
max=${x}
fi
# Если min не делится без остатка на $divisibleBy,
#+ то привести его к ближайшему подходящему числу из заданного диапазона.
if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
if [ ${min} -lt 0 ]; then
min=$((min/divisibleBy*divisibleBy))
else
min=$((((min/divisibleBy)+1)*divisibleBy))
fi
fi
# Если max не делится без остатка на $divisibleBy,
#+ то привести его к ближайшему подходящему числу из заданного диапазона.
if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
if [ ${max} -lt 0 ]; then
max=$((((max/divisibleBy)-1)*divisibleBy))
else
max=$((max/divisibleBy*divisibleBy))
fi
fi
# ---------------------------------------------------------------------
# А теперь собственно нахождение псевдослучайного числа.
# Обратите внимание: чтобы получить псевдослучайное число в конце диапазона
#+ необходимо рассматривать диапазон от 0 до
#+ abs(max-min)+divisibleBy, а не abs(max-min)+1.
# Этим превышением верхней границы диапазона можно пренебречь
#+ поскольку эта новая граница никогда не будет достигнута.
# Если использовать при вычислении формулу abs(max-min)+1,
#+ то будут получаться вполне корректные значения, но при этом,
#+ возвращаемые значения будут значительно ниже
#+ верхней границы диапазона.
# ---------------------------------------------------------------------
spread=$((max-min))
[ ${spread} -lt 0 ] && spread=$((0-spread))
let spread+=divisibleBy
randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
return 0
}
# Проверка функции.
min=-14
max=20
divisibleBy=3
# Создадим массив, который будет содержать счетчики встречаемости каждого из чисел
#+ в заданном диапазоне.
declare -a answer
minimum=${min}
maximum=${max}
if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
if [ ${minimum} -lt 0 ]; then
minimum=$((minimum/divisibleBy*divisibleBy))
else
minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
fi
fi
# Если max не делится без остатка на $divisibleBy,
#+ то привести его к ближайшему подходящему числу из заданного диапазона.
if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
if [ ${maximum} -lt 0 ]; then
maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
else
maximum=$((maximum/divisibleBy*divisibleBy))
fi
fi
# Необходимое условие при работе с массивами --
#+ индекс массива должен быть положительным числом,
#+ поэтому введена дополнительная переменная displacement, которая
#+ гарантирует положительность индексов.
displacement=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+displacement]=0
done
# Цикл с большим количеством итераций, чтобы посмотреть -- что мы получаем.
loopIt=1000 # Автор сценария предложил 100000 итераций,
#+ но в этом случае цикл работает чересчур долго.
for ((i=0; i<${loopIt}; ++i)); do
# Обратите внимание: числа min и max передаются функции в обратном порядке,
#+ чтобы продемонстрировать, что функция обрабатывает их корректно.
randomBetween ${max} ${min} ${divisibleBy}
# Вывод сообщения об ошибке, если функция вернула некорректное значение.
[ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo Выход за границы диапазона MIN .. MAX - ${randomBetweenAnswer}!
[ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo Число не делится на заданный делитель без остатка - ${randomBetweenAnswer}!
# Записать полученное число в массив.
answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
done
# Проверим полученные результаты
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
[ ${answer[i+displacement]} -eq 0 ] && echo "Число $i не было получено ни разу." || echo "Число ${i} встречено ${answer[i+displacement]} раз."
done
exit 0
|
Насколько случайны числа, возвращаемые функцией $RANDOM?
Лучший способ оценить "случайность" генерируемых
чисел -- это написать сценарий, который будет имитировать
бросание игрального кубика достаточно большое число раз, а
затем выведет количество выпадений каждой из граней...
Пример 9-26. Имитация бросания кубика с помощью
RANDOM
#!/bin/bash
# Случайные ли числа возвращает RANDOM?
RANDOM=$$ # Инициализация генератора случайных чисел числом PID процесса-сценария.
PIPS=6 # Кубик имеет 6 граней.
MAXTHROWS=600 # Можете увеличить, если не знаете куда девать свое время.
throw=0 # Счетчик бросков.
zeroes=0 # Обнулить счетчики выпадения отдельных граней.
ones=0 # т.к. неинициализированные переменные - "пустые", и не равны нулю!.
twos=0
threes=0
fours=0
fives=0
sixes=0
print_result ()
{
echo
echo "единиц = $ones"
echo "двоек = $twos"
echo "троек = $threes"
echo "четверок = $fours"
echo "пятерок = $fives"
echo "шестерок = $sixes"
echo
}
update_count()
{
case "$1" in
0) let "ones += 1";; # 0 соответствует грани "1".
1) let "twos += 1";; # 1 соответствует грани "2", и так далее
2) let "threes += 1";;
3) let "fours += 1";;
4) let "fives += 1";;
5) let "sixes += 1";;
esac
}
echo
while [ "$throw" -lt "$MAXTHROWS" ]
do
let "die1 = RANDOM % $PIPS"
update_count $die1
let "throw += 1"
done
print_result
# Количество выпадений каждой из граней должно быть примерно одинаковым, если считать RANDOM достаточно случайным.
# Для $MAXTHROWS = 600, каждая грань должна выпасть примерно 100 раз (плюс-минус 20).
#
# Имейте ввиду, что RANDOM - это генератор ПСЕВДОСЛУЧАЙНЫХ чисел,
# Упражнение:
# ---------------
# Перепишите этот сценарий так, чтобы он имитировал 1000 бросков монеты.
# На каждом броске возможен один из двух вариантов выпадения - "ОРЕЛ" или "РЕШКА".
exit 0
Как видно из последнего примера, неплохо было бы
производить переустановку начального числа генератора
случайных чисел RANDOM перед тем,
как начать работу с ним. Если используется одно и то же
начальное число, то генератор RANDOM
будет выдавать одну и ту же последовательность чисел. (Это
совпадает с поведением функции random() в языке C.)
Пример 9-27. Переустановка RANDOM
#!/bin/bash
# seeding-random.sh: Переустановка переменной RANDOM.
MAXCOUNT=25 # Длина генерируемой последовательности чисел.
random_numbers ()
{
count=0
while [ "$count" -lt "$MAXCOUNT" ]
do
number=$RANDOM
echo -n "$number "
let "count += 1"
done
}
echo; echo
RANDOM=1 # Переустановка начального числа генератора случайных чисел RANDOM.
random_numbers
echo; echo
RANDOM=1 # То же самое начальное число...
random_numbers # ...в результате получается та же последовательность чисел.
#
# В каких случаях может оказаться полезной генерация совпадающих серий?
echo; echo
RANDOM=2 # Еще одна попытка, но с другим начальным числом...
random_numbers # получим другую последовательность.
echo; echo
# RANDOM=$$ в качестве начального числа выбирается PID процесса-сценария.
# Вполне допустимо взять в качестве начального числа результат работы команд 'time' или 'date'.
# Немного воображения...
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
# Псевдослучайное число забирается
#+ из системного генератора псевдослучайных чисел /dev/urandom ,
#+ затем конвертируется в восьмеричное число командой "od",
#+ и наконец "awk" возвращает единственное число для переменной SEED.
RANDOM=$SEED
random_numbers
echo; echo
exit 0
|
Системный генератор /dev/urandom дает последовательность
псевдослучайных чисел с более равномерным
распределением, чем $RANDOM.
Команда dd
if=/dev/urandom of=targetfile bs=1
count=XX создает файл, содержащий
последовательность псевдослучайных чисел. Однако, эти
числа требуют дополнительной обработки, например с
помощью команды od (этот прием
используется в примере выше) или dd (см. Пример
12-45).
Есть и другие способы генерации псевдослучайных
последовательностей в сценариях. Awk имеет для этого достаточно
удобные средства.
Пример 9-28. Получение псевдослучайных
чисел с помощью awk
#!/bin/bash
# random2.sh: Генерация псевдослучайных чисел в диапазоне 0 - 1.
# Используется функция rand() из awk.
AWKSCRIPT=' { srand(); print rand() } '
# Команды/параметры, передаваемые awk
# Обратите внимание, функция srand() переустанавливает начальное число генератора случайных чисел.
echo -n "Случайное число в диапазоне от 0 до 1 = "
echo | awk "$AWKSCRIPT"
exit 0
# Упражнения:
# ---------
# 1) С помощью оператора цикла выведите 10 различных случайных чисел.
# (Подсказка: вам потребуется вызвать функцию "srand()"
# в каждом цикле с разными начальными числами.
# Что произойдет, если этого не сделать?)
# 2) Заставьте сценарий генерировать случайные числа в диапазоне 10 - 100
# используя целочисленный множитель, как коэффициент масштабирования
# 3) То же самое, что и во втором упражнении,
# но на этот раз случайные числа должны быть целыми.
Кроме того, команда date
так же может использоваться для генерации псевдослучайных целых
чисел.
|
Назад | Вперед
Содержание (общее) | Содержание раздела | Содержание подраздела
Если Вы не нашли что искали, то рекомендую воспользоваться поиском по сайту:
|
|
|