··number=$1
··# 3 и 2 — простые числа, 1 — нет.
··if [$number −lt 2]; then
····echo "No, $number is not a prime"
····exit 0
··fi
··# Теперь выполним некоторые вычисления.
·· while [$counter −le $(expr $number / −a— a $remainder −ne 0]
··do
····remainder=$(expr $number % $counter) # '/’ деление, '%' остаток
····# echo " for counter $counter, remainder = $remainder"
····counter=$(expr $counter + 1)
··done
··if [$remainder −eq 0]; then
····echo "No, $number is not a prime"
··else
····echo "Yes, $number is a prime"
··fi
··exit 0
Как это работает
Основу сценария составляет цикл while , поэтому уделим ему основное внимание. Для числа 77 условное выражение в заголовке цикла приняло бы вид:
while [2 −le 38 −a 1 −ne 0]
Очевидно, что оно не выполняется: 77 не делится нацело на 2. Каждый раз, когда код проверяет потенциальный делитель ($counter) и обнаруживает, что исходное число не делится на него нацело, он вычисляет остаток ($number % $counter), увеличивает $counter на 1 и повторяет вычисления.
Запуск сценария
Давайте выберем несколько чисел, которые на первый взгляд кажутся простыми, и проверим их, как показано в листинге 12.8.
Листинг 12.8. Опробование сценария isprime с несколькими числами
$ isprime 77
No, 77 is not a prime
$ isprime 771
No, 771 is not a prime
$ isprime 701
Yes, 701 is a prime
Если вам любопытно, раскомментируйте команду echo в цикле while, чтобы увидеть, как выполняются вычисления, и получить представление, насколько быстро или медленно сценарий находит делитель, делящий число нацело, без остатка. В листинге 12.9 показано, что в этом случае получается при попытке проверить число 77.
Результаты
Листинг 12.9. Запуск сценария с раскомментированной отладочной строкой
$ isprime 77
··for counter 2, remainder = 1
··for counter 3, remainder = 2
··for counter 4, remainder = 1
··for counter 5, remainder = 2
··for counter 6, remainder = 5
··for counter 7, remainder = 0
No, 77 is not a prime
Усовершенствование сценария
Сценарий реализует не самый эффективный алгоритм проверки числа, который работает тем медленнее, чем больше число. Например, взгляните на условие в цикле while. Сценарий раз за разом продолжает вычислять выражение $(expr $number / 2), тогда как его можно было вычислить только один раз и использовать вычисленное значение во всех последующих итерациях, сэкономив время на запуске подоболочки и вызове команды expr для вычисления значения, которое не изменяется ни на йоту до самой последней итерации.
Существуют также другие, не такие примитивные способы проверки простых чисел, включая замечательный алгоритм с интересным названием «Решето Эратосфена», более современный алгоритм «Решето Сундарама» или более сложный — «Решето Аткина». Поищите их описания в Интернете и проверьте с их помощью номер своего телефона (без дефисов!).
№ 87. Игральные кости
Это удобный сценарий для всех, кто любит играть в настольные игры, особенно ролевые, такие как Dungeons & Dragons.
В таких играх обычно бросаются кости, это может быть и один двадцатигранник, и шесть шестигранников, но в любом случае процесс основан на вероятностях. Игральные кости — по сути, простейший генератор случайных чисел, независимо от того, сколько таких генераторов задействовано в игре: один, два (Monopoly или Trouble) или больше.
Все эти случаи, как оказывается, легко смоделировать, что и делает сценарий в листинге 12.10, позволяющий указать количество и тип костей и затем «бросающий» их и возвращающий сумму.
Код
Листинг 12.10. Сценарий rolldice
#!/bin/bash
# rolldice — анализирует количество и тип костей и имитирует их броски.
#·· Примеры: d6 = один шестигранник
#············2d12 = два двенадцатигранника у каждого
#············d4 3d8 2d20 = один четырехгранник, три восьмигранника
#··························и два двадцатигранника.
rolldie()
{
··dice=$1
··dicecount=1
··sum=0
··# Первый шаг: представить аргумент в формате MdN.
·· if [-z "$(echo $dice | grep 'd')"]; then
····quantity=1