Книга: Искусство программирования на языке сценариев командной оболочки

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? Лучший способ оценить "случайность" генерируемых чисел -- это написать сценарий, который будет имитировать бросание игрального кубика достаточно большое число раз, а затем выведет количество выпадений каждой из граней...

Пример 9-25. Имитация бросания кубика с помощью 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-26. Переустановка 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-42).

Есть и другие способы генерации псевдослучайных последовательностей в сценариях. Awk имеет для этого достаточно удобные средства.

Пример 9-27. Получение псевдослучайных чисел с помощью 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) То же самое, что и во втором упражнении,

# но на этот раз случайные числа должны быть целыми.

Оглавление книги


Генерация: 1.178. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз