#!/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
Последние комментарии