- •1.1 Знакомство с интерпретатором Hugs.
- •1.2 Выполнение математических операций в интерпретаторе.
- •1.3. Простейшие генераторы списков.
- •1.4 Логические функции, функции сравнения, функции работы с перечислимыми типами данных.
- •1.5 Простейшие списочные и кортежные функции.
- •Задание на лабораторную работу №1.
- •Вариант 1.
- •Вариант 2.
- •Вариант 3.
- •Вариант 4.
- •Вариант 5.
- •Вариант 6.
- •Вариант 7.
- •Вариант 8.
- •Вариант 9.
- •Вариант 10.
- •Пример выполнения лабораторной работы 1.
- •Лабораторная работа 2. Создание простейших рекурсивных программ. Функции работы со строками и множествами. Сообщения об ошибках и преобразования типов.
- •2.1 Создание простейших рекурсивных программ.
- •2.2 Функции работы со строками и множествами.
- •2.3 Сообщения об ошибках и преобразования типов
- •Задание на лабораторную работу 2.
- •Вариант 1.
- •Вариант 2.
- •Вариант 3.
- •Вариант 4.
- •Вариант 5.
- •Вариант 6.
- •Вариант 7.
- •Вариант 8.
- •Вариант 9.
- •Вариант 10.
- •Пример выполнения работы
- •Лабораторная работа 3. Функции высших порядков.
- •Задание на лабораторную работу 3.
- •Вариант 1.
- •Вариант 2.
- •Вариант 3.
- •Вариант 4.
- •Вариант 5.
- •Вариант 6.
- •Вариант 7.
- •Вариант 8.
- •Вариант 9.
- •Вариант 10.
- •Лабораторная работа 4. Текстовые файлы. Факторизация, простые числа, разные задачи.
- •4. 1 Работа с текстовыми файлами в Haskell
- •Задание на лабораторную работу 4.
- •Вариант 1.
- •Вариант 2.
- •Вариант 3.
- •Вариант 4.
- •Вариант 5.
- •Вариант 6.
- •Вариант 7.
- •Вариант 8.
- •Вариант 9.
- •Вариант 10.
- •Лабораторная работа 5. Управление выводом в Прологе. Простейшие рекурсивные программы.
- •5.1 Факты и правила. База знаний. Запросы.
- •5.2 Управление выводом.
- •5.3 Рекурсия
- •Задание на лабораторную работу 5.
- •Вариант 1
- •Вариант 2
- •Вариант 3
- •Вариант 4
- •Вариант 5
- •Вариант 6
- •Вариант 7
- •Вариант 8
- •Вариант 9
- •Вариант 10
- •Лабораторная работа №6. Работа со списками в Прологе.
- •6.1 Списки в Прологе.
- •6.2 Алгоритмы обработки списков
- •6.3 Алгоритмы сортировки
- •Лабораторная работа № 7. Решение логических задач на Прологе.
- •Пример выполнения работы.
- •Лабораторная работа № 8.
5.3 Рекурсия
В предыдущих разделах мы видели, что организовать циклы и ветвления в Прологе можно с использованием механизма поиска с возвратом. Однако, основным инструментом организации циклов в Прологе, так же как и в Haskell, является рекурсия.
Классическим примером, демонстрирующим рекурсию, является вычисление факториала натурального числа. По определению факториала
1!=1 - факториал единицы равен единице,
n!=(n-1)!*n - факториал какого-то числа равен факториалу числа, меньшему на единицу, умноженному на это число.
Как видим, само определение факториала можно назвать рекурсивным. Попробуем записать процедуру вычисления факториала, руководствуясь этим определением:
fact(1,1). /* базис рекурсии – факториал единицы равен единице*/
fact(N,F):-M=N-1, /*запишем в новую переменную M значение N-1*/
fact(M,FM), /*вычислим факториал M, результат окажется в переменной FM*/
F=N*FM. /*умножим FM на N*/
Однако при запуске программы для вычисления факториала какого-нибудь натурального числа произойдет ошибка – переполнение стека. Попробуем разобраться, почему так произошло. Например, сделаем запрос fact(3,X). Попытка сопоставления с первым фактом fact(1,1) будет безуспешной. Произойдет вызов второго правила, переменная N станет связанной - N=3. Первая подцель успешно выполнится, M=3-1=2. Далее произойдет вызов предиката fact с первым аргументом, равным 2. Первое сопоставление с фактом неуспешно, переход ко второму правилу, успешное выполнение первой подцели, вызов предиката fact с первым аргументом, равным 1. Успешное сопоставление с фактом, но заносится в стек точка возврата, так как есть еще правило с заголовком fact. Далее происходит успешный возврат из рекурсии и вычисление факториала 3. После чего включается механизм возврата и делается попытка вычислить факториал единицы по второму правилу. При этом M становится равной 0, после чего будет происходить бесконечный вызов предиката с аргументами -1,-2,…. Чтобы избежать этого, требуется убрать ненужную точку возврата при вычислении факториала единицы. Преобразуем первый факт в правило:
fact(1,1):-!.
Теперь предикат будет работать правильно и не скатываться в бесконечную рекурсию. Другим способом решения проблемы является добавление во второе правило подцели, проверяющей , что аргумент N больше 1:
fact(1,1).
fact(N,F):-N>1
M=N-1,
fact(M,FM),
F=N*FM.
Наиболее эффективным способом организации рекурсии является хвостовая рекурсия. Она характеризуется тем, что любой рекурсивный вызов предиката является последней подцелью в правиле, и, кроме того, более ранние подцели не должны содержать иметь точек возврата. В этом случае стек, в котором запоминаются состояния вызывающих предикатов, расходуется более экономно. Очевидно, пример приведенной процедуры вычисления факториала не является хвостовой рекурсией. Однако можно написать новую процедуру с хвостовой рекурсией, если увеличить число аргументов предиката. Пусть первые два аргумента имеют тот же смысл, что и раньше – число, для которого необходимо вычислить факториал, и результат. Добавим к ним еще два аргумента – третий аргумент будет счетчиком вызовов, а четвертый – хранить произведение чисел, начиная с единицы. То есть теперь мы будем сначала вычислять факториал 1, затем умножать результат на 2 и передавать его рекурсивно для вычисления факториала 3 и т.д.
fact1(N,F,C,R):-C<=N, !,/*пока счетчик не более, чем N*/
R1=C*R, /*новое произведение получается умножением текущего счетчика на старое*/
С1=С+1,/*увеличиваем счетчик*/
fact1(N,F,C1,R1). /*рекурсивно вызываем правило с новым счетчиком и новым произведением*/
fact1(_,F,_,F). /*если счетчик равен N, переписываем произведение в результат*/
Для вычисления факториала необходимо вызывать этот предикат с двумя последними параметрами, равными единице. Если это представляется неудобным, можно написать дополнительный предикат c привычными двумя параметрами, вызывающий fact1:
factorial(N,F):-fact1(N,F,1,1).