Функция на пространстве последовательностей над называется индуктивной, если найдётся такая функция , что для любого , любой будет выполняться равенство В переводе на человеческий язык это определение звучит так: функция называется индуктивной, если её значение для удлинённой (за счёт добавления элемента ) последовательности можно выразить через значение этой же самой функции для исходной последовательности и через добавляемый элемент.
Функция называется функцией перевычисления: она позволяет «перевычислить» индуктивную функцию при удлинении последовательности.
Задача вычисления различных функций на последовательностях очень распространена в программировании. Индуктивные функции среди прочих хороши тем, что алгоритм их вычисления очень прост.
Многократно воспользуемся определением индуктивной функции: Такое постепенное «разворачивание» формулы приводит к вычислительному алгоритму:
Подобные алгоритмы называют однопроходными, поскольку обработка последовательности в них ведётся за один проход: при каждом проходе цикла извлекается очередной элемент последовательности, обрабатывается, и больше программа не возвращается к этому элементу.
Часто последовательность представлена в программе как массив. При реализации алгоритма на Perl в этом случае цикл может выглядеть так:
Perlwhile(@sequence) { my $x=shift @sequence; … }
или так:
Perlfor my $x(@sequence) { … }
В последнем случае массив @sequence
остаётся нетронутым;
в первом же варианте массив постепенно опустошается.
Если мы имеем дело с символьной последовательностью, представленной как строка
$sequence
, цикл принимает следующий вид:
Perlfor(my $i=0; $i<length $sequence; $i++) { my $x=substr $sequence, $i, 1; … }
Ещё лаконичней такой вариант:
Perlfor my $x(split //, $sequence) { … }
(выражение в круглых скобках — это строка $sequence
,
разбитая в список символов).
Ещё один важный способ получения элементов последовательности — чтение из файла. Чаще всего элементами служат или символы, или строки. В первом случае годится цикл
Perlmy $x; while(read $sequence, $x, 1) { … }
а во втором —
Perlwhile(my $x=<$sequence>) { … }
Поскольку добавление элемента в конец последовательности увеличивает длину на единицу, получаем то есть Как видно, получившаяся функция фактически не зависит от , но в этом нет никакого криминала. Существование функции доказывает индуктивность функции .
Добавление элемента к последовательности увеличивает сумму на величину добавляемого элемента:
Сумма, таким образом, тоже оказалась индуктивной функцией. Однако есть одна проблема: что считать суммой пустой последовательности? Что-то подсказывает нам, что это ноль, но это лишь наше смутное ощущение. На самом деле мы можем обосновать этот ноль совершенно формально: откуда . Иными словами, единственный способ доопределить сумму для пустой последовательности, не нарушив индуктивности — положить её равной нулю.
Так выглядит реализация алгоритма вычисления суммы чисел из командной строки:
#!/usr/bin/perl use warnings; my $sum=0; for my $x(@ARGV) { $sum+=$x; } print "$sum\n";
Случай произведения элементов последовательности во всём похож на случай суммы:
Точно так же, как и для суммы, устанавливается индуктивность:
Уже знакомая нам проблема доопределения функции для пустой последовательности решается аналогично: и значит, .
Программа:
#!/usr/bin/perl use warnings; my $prod=1; for my $x(@ARGV) { $prod*=$x; } print "$prod\n";
Эта функция индуктивна, поскольку
Очевидно, что для пустой последовательности функцию можно доопределить как угодно, не нарушая индуктивности.
Программа:
#!/usr/bin/perl use warnings; my $last=undef; for my $x(@ARGV) { $last=$x; } print "$last\n";
Пусть задано число . Требуется вычислить значение многочлена Можно представлять себе искомое значение как значение функции на последовательности коэффициентов многочлена, расположенных по убыванию степеней . Обозначим эту функцию как (почему именно так, выяснится позже). Как и раньше, исследуем эту функцию на предмет её индуктивности.
Преобразуем выражение для многочлена: Теперь видно, что происходит со значением многочлена при добавлении в последовательность коэффициентов ещё одного: старое значение умножается на , после чего прибавляется новый коэффициент:
Легко видеть, что для пустой последовательности функция доопределяется, исходя из равенства , следовательно, при ненулевом . При нулевом можно выбрать произвольное значение.
Следует заметить, что функция при превращается в , а при — в .
Приведённый способ вычисления значения многочлена в точке называется схемой Горнера. Этот способ очень эффективный, поскольку требует, помимо сложения, лишь операций умножения, и вовсе не нуждается в возведениях в степень (мы полагаем, что возведение в степень, даже в натуральную, требует бо́льших вычислительных усилий, чем умножение).
Следующая программа вычисляет значение многочлена в заданной точке по схеме Горнера. Число и коэффициенты многочлена в порядке убывания степеней заданы, как обычно, в командной строке:
#!/usr/bin/perl use warnings; my $t=shift; my $p=0; for my $x(@ARGV) { $p=$p*$t+$x; } print "$p\n";
По определению где . Заметим, что таких может быть и несколько, однако, к счастью, все они равны между собой.
Эта функция индуктивна, поскольку Таким образом,
Увы, функция не определена для пустой последовательности. Попытка доопределить функцию с сохранением индуктивности приводит к равенству которое означает, что должно быть не меньше любого . Числа с таким свойством не существует, однако, если в последовательности могут встретиться числа лишь из ограниченного числового множества (на практике так обычно и бывает), то можно взять наибольшее число из этого множества в качестве . В теории, когда может принимать любое, сколь угодно большое значение, можно позволить индуктивной функции принимать, помимо числовых значений, ещё идеальное значение , от которого требуется лишь выполнение неравенства для всех .
Совершенно аналогично устанавливается индуктивность максимума, для которого Для пустой последовательности максимум доопределяется значением .
Приводим программу, вычисляющую минимум и максимум чисел, заданных в командной строке:
#!/usr/bin/perl use warnings; my $min=INFINITY; my $max=-INFINITY; for my $x(@ARGV) { ($min, $max)=($x<$min? $x: $min, $x>$max? $x: $max); } print "$min\t$max\n";
Функция принимает значения или в зависимости от того, содержится ли значение среди элементов .
Эта функция индуктивна: её функция перевычисления равна
Программа берёт и элементы
последовательности из командной строки и печатает YES
или
NO
:
#!/usr/bin/perl use warnings; my $a=shift; my $search=0; for my $x(@ARGV) { $search||=($x==$a); } print $search? 'YES': 'NO', "\n";
У читателя уже могло сложиться впечатление, что все функции на пространстве последовательностей являются индуктивными. Это не так, и среднее арифметическое тому подтверждение. Для доказательства достаточно рассмотреть пример. Если бы существовала такая функция , что выполнялись бы равенства которые, очевидно, не могут выполняться одновременно.
Рассмотрим последовательности символов. Требуется вычислить количество слов в последовательности, если под словами понимаются непрерывные фрагменты в последовательности, состоящие из непробельных символов. Каждое слово, как правило, заключено между двумя пробельными символами. В особом случае слово примыкает к началу последовательности справа или к концу слева. Да, пустые слова (между двумя соседними пробельными символами) не считаются.
С точки зрения поставленной задачи все символы делятся на две категории: пробельные и непробельные. Никакие другие свойства символов не важны, поэтому можно кодировать символы логическими значениями (пробелы) и (непробелы). Так что будем иметь дело с последовательностями логических значений . Искомую функцию обозначим (Word Count — счётчик слов).
Понятно, что знания и недостаточно для вычисления : счётчик слов увеличивается в том и только в том случае, если добавляемый элемент — непробел, а последний элемент — пробел (то есть происходит смена пробела на непробел). В результате получим выражение
Вывод: функция не индуктивна.
Имеется кандидатов, из которых должен быть выбран один по итогам голосования. Пронумеруем кандидатов числами , что позволит отождествить множество кандидатов с числовым множеством . Последовательность задаёт процесс голосования — это последовательность голосов, поданных за того или иного кандидата. Победителем объявляется номер кандидата, за которого подано больше всего голосов. Во избежание неопределённости, если несколько кандидатов наберут максимальное количество голосов, победителем будет тот из них, у кого больше номер. Функция вычисляет победителя.
Как и в случае среднего арифметического, пример доказывает, что функция не индуктивна: то есть информации, заключённой в и , недостаточно для вычисления .
Хочется верить, что функция индуктивна, по крайней мере, на эту мысль наводит равенство (удлинение последовательности не меняет её первого элемента). Но тогда получится, что для любой последовательности будет выполнено при том, что это значение можно выбрать совершенно произвольно. Правильно было бы написать но тогда для вычисления придётся знать не только и , но и . Всё это означает, что функция не индуктивна.
В некоторых случаях можно сократить вычисление значения индуктивной функции за счёт устранения ненужных действий. Такая ситуация наступает, если после обработки части последовательности вычисленное значение индуктивной функции окажется стационарным, то есть таким, что никакое дальнейшее перевычисление не может его изменить. Более строго, значение называется стационарным, если . Если , то, очевидно, , и, как бы мы ни удлиняли в дальнейшем последовательность, значение функции останется тем же самым стационарным значением . Это означает, что, получив стационарное значение, можно прервать цикл обработки последовательности.
С учётом сказанного, внесём в алгоритм вычисления индуктивной функции дополнение:
Из разобранных к настоящему моменту примеров индуктивных функций стационарные значения имеются у функций : , : , : и : .
Ещё пара примеров функций на пространстве последовательностей логических значений. Для индуктивной функции стационарным значением служит , а для функции — .