Выбрать главу

··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