Первым делом отправим число — количество строк треугольника (оно должно
задаваться в командной строке) — в переменную $n
:
Perlmy $n=shift;
К сожалению, может так случиться, что пользователь программы, запуская её, не укажет в командной строке никакого числа. В этом случае продолжать выполнение программы бессмысленно, и лучше программу прервать, заодно намекнув пользователю, как ей пользоваться.
Как программа узнает, что число не указано? Отсутствие параметров в командной
строке означает, что массив @ARGV
пуст. Команда $n=shift
(то же самое в данном случае, что и $n=shift @ARGV
) пытается извлечь из массива
@ARGV
самый первый элемент и отправить его в переменную
$n
. В случае пустого массива вызов встроенной процедуры
shift
возвратит неопределённое значение. Оно и будет
присвоено переменной.
Можно было бы проверить значение $n
на определённость
процедурой defined
, и, если значение окажется
неопределённым, завершить программу, выдав сообщение об ошибке:
Perlunless(defined $n) { die "$0: Нужно целое положительное число!\n"; }
Мы же предпочитаем более лаконичный код, делающий то же самое:
Perlmy $n=shift or die "$0: Нужно целое положительное число!\n";
Поясним. Здесь оператором логического «ИЛИ» связаны два выражения: my $n=shift
и die …
. Если
значение первого выражения окажется истинным, вычислять второе нет нужды, так
как значение всей операции «ИЛИ» в любом случае окажется истинным. Значением
присваивания будет то, что присваивается, то есть число, или, возможно,
неопределённое значение. Любое ненулевое число там, где должно быть логическое
значение, трактуется как истинное, а ноль — как ложное. Неопределённие значение
тоже считается ложным в логическом контексте. Итак, ноль в командной строке или
отсутствующий параметр сделают левую часть оператора or ложной,
и потребуется вычислить правую часть, чьё значение теперь и будет значением
оператора or. Вычисление правой части потребует вызова процедуры
die
, которая выдаст сообщение об ошибке и прервёт
выполнение программы.
Переменная $0
, напоминаем, содержит имя файла с программой:
%
./pascal-triangle.pl
./pascal-triangle.pl: Нужно целое положительное число!
Очередная строка треугольника формируется в массиве @a
.
Построение треугольника Паскаля начинается с массива, состоящего из одной
единицы:
Perlmy @a=(1);
В цикле, который прокрутится $n
раз, числа из массива
@a
выводятся, разделённые символами табуляции. Затем массив
@a
меняется.
Как мы выяснили, следующая строка в треугольнике Паскаля получается
поэлементным сложением массива @a
с точно таким же, но
в котором все числа сдвинуты вправо на одну позицию. Можно сделать копию
массива @a
в массиве @b
, а затем сдвинуть
его элементы вправо, вставив в его начало ноль:
Perlmy @b=@a; unshift @b, 0;
Но того же результата можно добиться и короче:
Perlmy @b=(0, @a);
Прибавление к элементам массива @a
элементов массива
@b
(с теми же индексами) производим в цикле:
Perlfor(my $i=1; $i<@b; $i++) { $a[$i]+=$b[$i]; }
Здесь нужно обратить внимание на то, что последний проход цикла состоится при
$i
, равном индексу последнего элемента массива
@b
, который на один элемент длиннее, чем
@a
. В теле цикла делается попытка увеличить несуществующий
элемент $a[$i]
. Не вызовет ли это ошибку во время исполнения
программы? Нет, не вызовет. Недостающий элемент будет создан, и в него
отправится ноль, и всё это произойдёт без вмешательства программиста. Perl
допускает подобное хулиганство.
Итак, приводим код цикла, в котором выводятся и строятся строки треугольника:
Perlfor(1..$n) { print join("\t", @a), "\n"; my @b=(0, @a); for(my $i=1; $i<@b; $i++) { $a[$i]+=$b[$i]; } }
Это решение работает, но оно малодушное. Зачем-то мы завели массив
@b
, содержащий те же числа, что и @a
,
и ещё добавочный и нигде не используемый ноль в начале. Но ведь вся необходимая
для получения следующей строки треугольника информация содержится в предыдущей.
Нельзя ли обойтись без копирования строки?
Поступим так. В начало массива @a
вставим ноль. Затем,
проходя по всем элементам @a
кроме последнего по порядку,
будем прибавлять к ним следующий элемент (он пока не изменился):
Perlunshift @a, 0; for(my $i=0; $i<@a-1; $i++) { $a[$i]+=$a[$i+1]; }
Массив @b
стал не нужен.
Мы сэкономили (чуть-чуть) память, мы сэкономили работу процессора, необходимую для копирования массива, а код программы стал немного проще. Единственная плата за эту экономию — небольшое интеллектуальное напряжение, которое всегда полезно. Вряд ли мы заметим результат наших улучшений, запуская программу. Однако поиск более эффективного алгоритма — святая обязанность программиста, и в других задачах подобные улучшения могут здорово ускорить программу и сберечь память.