
- •Методическое пособие
- •Архангельск,
- •«К 2011 году большинство программ будут писать компьютеры…».
- •История и перспективы развития языка Пролог
- •2. Краткая инструкция по работе в среде pie32
- •3. Механизм вывода в системе Пролог. Примеры Термы и объекты
- •Запросы к базе данных
- •Унификация
- •Правила
- •Рекурсивные процедуры
- •Базы знаний
- •Решение логических задач
- •Арифметические выражения
- •Примеры программ
- •Часть II. Указания к выполнению контрольной работы
- •Введение
- •Генеалогическое дерево
- •Перевод генеалогического дерева в базу фактов
- •Составление правил искомых родственных связей в базе знаний
- •Лабораторная работа №3
- •Список литературы
Примеры программ
Рассмотрим некоторые программы, демонстрирующие обработку числовых данных. Отметим важную особенность процедур, создаваемых на языке Прологе: они, в отличии от встроенных функций, не могут появляться в арифметических выражениях. Если требуется, например, переменной R присвоить значение, равное умноженному на три большему из двух выражений X и Y, то, используя определенную ниже процедуру максимум, это можно записать так:
максимум(X,Y,Z), R is 3*Z.
Пример: Написать процедуру, вычисляющую максимум из двух чисел.
максимум(X,X,X).
максимум(X,Y,X):- X>Y.
максимум(X,Y,Y):- X<Y.
В предикате максимум/3 третий аргумент является максимумом из двух чисел - первого и второго аргументов. Нетрудно понять смысл каждого из правил данной процедуры. Посмотрим на реакцию интерпретатора Пролога на запросы, содержащие данный предикат.
?- максимум(20,50,X).
X = 50
Yes
?- максимум(100,50,X).
X = 100
Yes
?- максимум(X,50,100).
X = 100
Yes
Последний ответ показывает, что наш предикат позволяет находить ответ на вопросы типа: "Каково должно быть число, чтобы максимум из искомого числа и числа 50, равнялся бы 100?".
Задание Объясните, почему интерпретатор Пролога ответил "No" в следующем запросе:
?- максимум(X,50,40).
No
Пример: Составить процедуру гипотенуза/3, которая по двум катетам прямоугольного треугольника вычисляет его гипотенузу.
Воспользуемся теоремой Пифагора и встроенной функцией sqrt для вычисления квадратного корня:
гипотенуза(X,Y,Z):- Z is sqrt(X**2 + Y**2).
Программа корректно вычисляет гипотенузу, но если мы попробуем при ее помощи найти один из катетов, то убедимся, что процедура работает не вполне правильно. Чтобы избежать этого добавим проверку того, что первые два аргумента предиката - числа, для чего используем встроенный предикат integer/1:
гипотенуза(X,Y,Z):- integer(X), integer(Y),
Z is sqrt(X**2 + Y**2).
Теперь наша программа работает корректно:
?- гипотенуза(3,4,X).
X = 5
Yes
?- гипотенуза(3,'a',X).
No
?- гипотенуза(3,X,5).
No
Пример: Написать предикат, который по двум парам чисел - длинам катетов прямоугольных треугольников – определяет величину меньшей из гипотенуз.
Воспользуемся процедурой гипотенуза/3, разобранной выше, и встроенной функцией min/2:
мин_гип(A1,B1,A2,B2,Min):-
гипотенуза(A1,B1,C1),
гипотенуза(A2,B2,C2),
Min is min(C1,C2).
Запросы к интерпретатору Пролога могут выглядеть так
?- мин_гип(3,4,8,6,X).
X = 5
Yes
?- мин_гип(3,4,Y,6,X).
No
Пример: Факториалом натурального числа n называют произведение всех целых чисел от 1 до n включительно. Для записи факториала числа n используют обозначение n!:
n!=n*(n-1)*(n-2)*...*2*1=n*(n-1)!
Следующая процедура вычисляет факториал числа. Обратите внимание на использование рекурсии в данной процедуре:
факториал(1,1).
факториал(N,R):- integer(N), N>1, N1 is N-1,
факториал(N1,R1), R is N*R1.
Первое правило (так называемый терминальный случай, то есть тот момент выполнения процедуры, когда она перестает вызывать сама себя) гласит, что факториал единицы равен единице.
Второе правило есть просто запись определения факториала: результат R получается умножением числа N на факториал числа, меньшего на единицу. Оно будет срабатывать при всех n>1 потому, что интерпретатор Пролога просматривает базу данных сверху вниз и переходит к следующему правилу или факту только в том случае, когда он не может выполнить текущее правило.
Пример: Написать программу на языке Пролог, печатающую сумму всех цифр введенного с клавиатуры числа.
Для решения данной задачи воспользуемся двумя предикатами. Предикат сумма/2 имеет своим первым аргументом число, сумма цифр которого, является его вторым аргументом. Второй предикат - печать_суммы/0 - запрашивает число, вызывает предикат сумма/2 и печатает полученный результат.
сумма(X,Y):- integer(X), X<10, Y is X.
сумма(X,Y):- integer(X), X1 is X//10, сумма(X1,Y1),
Z is X mod 10, Y is Y1+Z.
печать_суммы:- write('Введите число (в конце точка): '),
read(X), nl, сумма(X,Y),
write('Сумма цифр числа '), write(X),
write(' равна '), write(Y), nl.
Правило печать_суммы не имеет аргументов, данные вводятся с клавиатуры и затем при помощи механизма унификации передаются другим подцелям данного правила.
В заключение приведем процедуру для решения известной головоломки "Ханойская башня", изобретенной французским математиком Люка в 1883 году и украшенной им же легендой:
"Где-то в непроходимых джунглях, недалеко от города Ханоя, есть монастырь бога Брамы. В начале времен, когда Брама создавал Мир, он воздвиг в этом монастыре три высоких алмазных стержня и на один из них возложил 64 диска, сделанных из чистого золота. Он приказал монахам перенести эту башню на другой стержень (в соответствии с правилами, разумеется). С этого времени монахи работают день и ночь. Когда они закончат свой труд, наступит конец света."
Правила перемещения дисков таковы: разрешается снимать со стержня только верхний диск, запрещается класть больший диск на меньший, при каждом ходе передвигается только один диск.
Процедура move/4 дает решение этой головоломки.
% move(число_дисков, откуда, куда, через)
move(1,X,Y,_) :- write('Move top disk from '),
write(X), write(' to '),
write(Y), nl.
move(N,X,Y,Z) :- N>1, M is N-1,
move(M,X,Z,Y),
move(1,X,Y,_),
move(M,Z,Y,X).
Запрос для решения задачи о трех дисках выглядит так
?- move(3,left,right,center).
Как видите, здесь снова применяется рекурсия. Попробуйте изменить процедуру так, чтобы сообщения выводились на русском языке. После этой попытки Вы поймете, почему с естественно-языковыми системами чаще всего общаются на английском языке.
Списки
Списки - одна из наиболее часто употребляемых структур в Прологе. При записи список заключают в квадратные скобки, а элементы списка разделяют запятыми, например,
[слон, лошадь, обезьяна, собака]
Это список из четырех атомов - слон, лошадь, обезьяна, собака.
Элементами списка могут быть любые термы Пролога, т.е. атомы, числа, переменные и составные термы. Это позволяет составлять списки из списков. Пустой список записывается как [].
Вот пример списка с несколько более сложной структурой:
[слон, [ ], X, предок(Х, том), [a,b,c], f(22)]
Первый элемент непустого списка называется головой, а остальная часть списка носит название хвост. У списка, состоящего только из одного элемента головой является этот единственный элемент, а хвостом - пустой список. Обозначение [H|T] используется для представления списка с головой H и хвостом T. Если символ | помещен перед последним термом списка, то это означает, что этот последний терм определяет другой список. Полный список получится, если соединить этот подсписок с последовательностью элементов, расположенных до черты.
В следующем примере 1 - голова списка, а [2, 3, 4, 5] - хвост. Пролог покажет это при помощи сопоставления списка чисел с образцом, состоящим из головы и хвоста.
?- [1, 2, 3, 4, 5] = [Head | Tail].
Head = 1
Tail = [2, 3, 4, 5]
Yes
Здесь Head и Tail - только имена переменных. Мы могли бы использовать X и Y или какие-нибудь другие имена переменных с тем же успехом. Заметим, что хвост списка всегда является списком. Голова, в свою очередь, есть элемент списка. Это относится и ко всем другим элементам, расположенным до вертикальной черты списка, что позволяет выделить, скажем, второй элемент списка.
Пример: Используем анонимные переменные для головы и списка, стоящего после черты, если нам нужен только второй элемент списка:
?- [слон, лошадь, осел, собака] = [_, X | _ ].
X = лошадь
Yes
Рассмотрим несколько процедур обработки списков. Обратите внимание, что все они используют рекурсию, в которой терминальное (базовое) правило использует пустой список.
Пример: Следующий предикат подсчитывает сумму всех элементов списка чисел.
сумма_списка([],0).
сумма_списка([H|T],S):- number(H), сумма_списка(T,S1), S is S1+H.
?- сумма_списка([0,1,7,2,456],X).
X = 466
Yes
Пример: Подсчитаем количество элементов произвольного списка.
колво_элементов([],0). % кол-во элементов пустого списка равно 0
колво_элементов([_|T],S):- колво_элементов (T,S1), S is S1+1.
?- колво_элементов([a,d,[d,t,[t]],s],X).
X = 4
Yes
Пример: Принадлежность элемента списку.
принадлежит(Х,[Х|_]). /* любой элемент списка всегда является головой
какого-нибудь подсписка */
принадпежит(Х,[_|Z) :- принадлежит(Х,Z).
?- принадлежит(седло,[руль,рама,колесо,седло])
Yes
Пример: Выбор n-го элемента списка.
n(1, E, [E|_]).
n(N,E,[_|L]) :- C is N - 1, n(C, E, L).
?- n(3,X,[1,2,3,4,5]).
X = 3
1 solution
Пример: Функция присоединения.
append([ ], L, L).
append([X|Ll], L2, [X|L3]) :- append(Ll,L2,L3).
Функция append позволяет составлять и расчленять список.
?- append([a,b,c], [d,e,f], L)
L = [a,b,c,d,e,f].
?- append(L, [d,e,f|, [a,b,c,d,e,f])
L = [a,b,c].
?- append([a,b,c], L, [a,b,c,d,e,f])
L = [d,e,f].
?- append(Ll, L2, [a,b,c,d])
L1= [], L2= [a,b,c,d]
L1= [a], L2= [b,c,d]
L1= [a,b], L2= [c,d]
L1= [a,b,c], L2= [d]
L1= [a,b,c,d], L2= []
Пример: Поиск наименьшего элемента списка.
меньший([X], X).
меньший([X, Y|Z], R) :- X =< Y, меньший([X|Z], R);
X > Y, меньший([Y|Z], R).
?- меньший([6,5,7,3,2,0],X)
X = 0
Пример: Удаление заданного элемента из списка.
удалить(E, [E | Y], Y).
удалить(E, [X | Y], [X | Z]) :- удалить(E, Y, Z).
?- удалить(7,[1,2,3,7,4,5],Y)
Y= [1,2,3,4,5]
Пример: Сортировка элементов списка.
сортировать([], []).
сортировать(L, [M|R]) :- меньший(L, M), удалить(M, L, Q),
сортировать(Q, R), !.
?- сортировать([1,5,2,7,3,4,2,9],X)
X= [1,2,2,3,4,5,7,9]
Пример: Перевод одного списка в другой, согласно словарю.
словарь("I";"я").
словарь("study","изучаю").
словарь("language","язык").
словарь("PROLOG","Пролог").
словарь("in","в").
словарь("the university","университете").
перевести ([], []).
перевести ([С_А|Ф_А], [С_Р|Ф_Р]):- словарь (С_А, С_Р),
перевести (Ф_А, Ф_Р).
?- перевести(["I","study","language","PROLOG","in","the university"],P)
Р = ["Я", "изучаю", "язык", "Пролог", "в", "университете"]
?- перевести(Р, ["Я", "изучаю", "язык", "Пролог", "в", "университете”]).
P = ["I", "study", "language", "PROLOG", "in", "the university"]
Пример: Предикат место/3 успешен, если третий аргумент есть список, полученный вставкой первого аргумента в произвольное место списка, являющимся вторым аргументом.
место(E, L, [E|L]).
место(E, [H|L], [H|Y]):- место(E, L,Y).
Посмотрим на результаты некоторых запросов, использующих этот предикат.
?- место(1,[2,3],X).
X = [1, 2, 3]
X = [2, 1, 3]
X = [2, 3, 1]
No
?- место(1,L,[2,1,3]).
L = [2, 3]
No
?- место(X,[2,3],[2,1,3]).
X = 1
No
Пример: Разбить список с элементами разделителями (0) на список списков (слов).
parser([],[],[]).
parser([X|Z],D,W) :- parser(Z,D1,W1),(
X \== 0, D = [X|D1], W = W1;
X == 0, D = [], W = [D1|W1]).
concat(Z,W) :- parser(Z,D1,W1), W = [D1|W1].
?- concat([a,s,d,f,0,r,t,e,0,w,d,0,g,h,j,0,r,t],X)
X= [[a,s,d,f],[r,t,e],[w,d],[g,h,j],[r,t]]
Пример: Разбить список с элементами разделителями (0) на список списков (слов).
calc([],0,[]).
calc([X|Z],D,W) :- calc(Z,D1,W1),(
X \== 0, D is D1 + 1, W = W1;
X == 0, D = 0, W = [D1|W1]).
conc(Z,W) :- calc(Z,D1,W1), W = [D1|W1].
?- conc([a,s,d,f,0,r,t,e,0,w,d,0,g,h,j,0,r,t],X)
X= [4,3,2,3,2]
Пример: Предикат перестановка/2 дает все перестановки первого аргумента.
перестановка([],[]).
перестановка([H|L],Z):- перестановка(L,Y), место(H,Y,Z).
Пример использования:
?- перестановка([a,b,c],X).
X = [a, b, c]
X = [b, a, c]
X = [b, c, a]
X = [a, c, b]
X = [c, a, b]
X = [c, b, a]
No
И, наконец, приведем правило для печати всех возможных перестановок списка:
все_перестановки(L):- перестановка(L,R), write(R), nl, fail.
Первая подцель предиката вычисляет очередную перестановку, печатает ее и переходит к последней подцели - fail. Эта подцель всегда неуспешна, что заставляет Пролог вернуться к началу правила и продолжить поиск решения. Работа процедуры завершится, когда все перестановки будут исчерпаны:
?- все_перестановки(['маркиза', 'ваши прекрасные глаза',
| 'сулят мне смерть от любви']).
[маркиза, ваши прекрасные глаза, сулят мне смерть от любви]
[ваши прекрасные глаза, маркиза, сулят мне смерть от любви]
[ваши прекрасные глаза, сулят мне смерть от любви, маркиза]
[маркиза, сулят мне смерть от любви, ваши прекрасные глаза]
[сулят мне смерть от любви, маркиза, ваши прекрасные глаза]
[сулят мне смерть от любви, ваши прекрасные глаза, маркиза]
No