Разработка

Первым делом отправим число — количество строк треугольника (оно должно задаваться в командной строке) — в переменную $n:

my $n=shift;

К сожалению, может так случиться, что пользователь программы, запуская её, не укажет в командной строке никакого числа. В этом случае продолжать выполнение программы бессмысленно, и лучше программу прервать, заодно намекнув пользователю, как ей пользоваться.

Как программа узнает, что число не указано? Отсутствие параметров в командной строке означает, что массив @ARGV пуст. Команда $n=shift (то же самое в данном случае, что и $n=shift @ARGV) пытается извлечь из массива @ARGV самый первый элемент и отправить его в переменную $n. В случае пустого массива вызов встроенной процедуры shift возвратит неопределённое значение. Оно и будет присвоено переменной.

Можно было бы проверить значение $n на определённость процедурой defined, и, если значение окажется неопределённым, завершить программу, выдав сообщение об ошибке:

unless(defined $n)
{
	die "$0: Нужно целое положительное число!\n";
}

Мы же предпочитаем более лаконичный код, делающий то же самое:

my $n=shift or die "$0: Нужно целое положительное число!\n";

Поясним. Здесь оператором логического «ИЛИ» связаны два выражения: my $n=shift и die. Если значение первого выражения окажется истинным, вычислять второе нет нужды, так как значение всей операции «ИЛИ» в любом случае окажется истинным. Значением присваивания будет то, что присваивается, то есть число, или, возможно, неопределённое значение. Любое ненулевое число там, где должно быть логическое значение, трактуется как истинное, а ноль — как ложное. Неопределённие значение тоже считается ложным в логическом контексте. Итак, ноль в командной строке или отсутствующий параметр сделают левую часть оператора or ложной, и потребуется вычислить правую часть, чьё значение теперь и будет значением оператора or. Вычисление правой части потребует вызова процедуры die, которая выдаст сообщение об ошибке и прервёт выполнение программы.

Переменная $0, напоминаем, содержит имя файла с программой:

Очередная строка треугольника формируется в массиве @a. Построение треугольника Паскаля начинается с массива, состоящего из одной единицы:

my @a=(1);

В цикле, который прокрутится $n раз, числа из массива @a выводятся, разделённые символами табуляции. Затем массив @a меняется.

Как мы выяснили, следующая строка в треугольнике Паскаля получается поэлементным сложением массива @a с точно таким же, но в котором все числа сдвинуты вправо на одну позицию. Можно сделать копию массива @a в массиве @b, а затем сдвинуть его элементы вправо, вставив в его начало ноль:

my @b=@a;
unshift @b, 0;

Но того же результата можно добиться и короче:

my @b=(0, @a);

Прибавление к элементам массива @a элементов массива @b (с теми же индексами) производим в цикле:

for(my $i=1; $i<@b; $i++)
{
	$a[$i]+=$b[$i];
}

Здесь нужно обратить внимание на то, что последний проход цикла состоится при $i, равном индексу последнего элемента массива @b, который на один элемент длиннее, чем @a. В теле цикла делается попытка увеличить несуществующий элемент $a[$i]. Не вызовет ли это ошибку во время исполнения программы? Нет, не вызовет. Недостающий элемент будет создан, и в него отправиться ноль, и всё это произойдёт без вмешательства программиста. Perl допускает подобное хулиганство.

Итак, приводим код цикла, в котором выводятся и строятся строки треугольника:

for(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 кроме последнего по порядку, будем прибавлять к ним следующий элемент (он пока не изменился):

unshift @a, 0;
for(my $i=0; $i<@a-1; $i++)
{
	$a[$i]+=$a[$i+1];
}

Массив @b стал не нужен.

Мы сэкономили (чуть-чуть) память, мы сэкономили работу процессора, необходимую для копирования массива, а код программы стал немного проще. Единственная плата за эту экономию — небольшое интеллектуальное напряжение, которое всегда полезно. Вряд ли мы заметим результат наших улучшений, запуская программу. Однако поиск более эффективного алгоритма — святая обязанность программиста, и в других задачах подобные улучшения могут здорово ускорить программу и сберечь память.

Информатика-54© А. Н. Швец