Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык программирования SWI-Prolog. Учебно-методическое пособие

.pdf
Скачиваний:
0
Добавлен:
15.08.2026
Размер:
830 Кб
Скачать

Представим таблицу в виде предиката данные(Имя, Возраст, Рост):

данные(игорь, 18, 189). данные(роман, 17, 182). данные(виктор, 20, 185). данные(антон, 20, 191).

данные(николай, 16, 193).

Имена пишем со строчной буквы, так как это не переменные, а значения. Альтернативный вариант – брать имена в кавычки, тогда их можно писать с заглавной буквы.

Составим ряд запросов к базе данных. В запросах используем операторы сравнения:

= (равно), < (меньше),

=< (равно или меньше), именно так, вариант <= не работает! > (больше), >= (больше или равно), \= (не равно).

Запрос 1. Выбрать всех спортсменов не ниже 184, и не выше 190 см.

данные(Х, _А, _В), _В>=184,_В=<190.

Символ подчеркивания блокирует вывод значения переменных _A и _B (но только если после него идёт заглавная буква).

Запрос 2. Выбрать всех спортсменов с ростом ниже 186 и старше 19 лет.

данные(X, _A, _B), _B<186,_A>19.

Запрос 3. Выбрать всех спортсменов, возраст которых не равен 20 годам.

данные(X, _A, _B), _A\=20.

Запрос 4. Выбрать всех спортсменов младше, чем Игорь.

данные(игорь, _V, _),

данные(X, A, _), A<_V.

Запрос 5. Вывести все записи базы данных.

Самый простой вариант: использовать предикат fail. Этот предикат вызывает неуспешное завершение и побуждает Пролог продолжить поиск. Без этого для продолжения поиска нужно нажать кнопкуNext (при работе на сайте swish.swi-prolog.org).

41

данные(Имя, Возраст, Рост),

write(Имя), write(Возраст), writeln(Рост), fail.

Для написания более сложных запросов можно использовать предикаты высших порядков findall, bagof, setof. Самый простой из них – предикат findall, результат всегда является одним списком, элементы которого идут в порядке появления записей в базе данных. Результат, возвращаемый предикатами bagof и setof, в общем случае зависит от вида запроса.

Напишем несколько запросов с использованием предиката findall. 1) Вывести имена и рост тех спортсменов, чей рост больше 190 см.

findall((Имя,Рост), (данные(Имя,_,Рост),Рост>190), Список).

Результат:

Список = [(антон,191), (николай,193)]

2) Определить самого молодого спортсмена (или спортсменов). Решение.

Формируем список возрастов спортсменов:

findall(Возраст, данные(_,Возраст,_), Список),

Определяем минимальное значение:

min_list(Список, МинВозраст),

Получаем данные спортсмена/спортсменов минимального возраста:

данные(Имя, МинВозраст, Рост).

Результат:

Имя = николай, МинВозраст = 16,

Рост = 193, Список = [18, 17, 20, 20, 16]

Сравним теперь предикаты findall и bagof. Выполним запрос

findall((Имя,Рост), данные(Имя,20,Рост), Список).

Получим список двадцатилетних спортсменов:

Список = [(виктор,185), (антон,191)]

Если же мы не конкретизируем возраст спортсмена, то получим имя и рост всех спортсменов в одном общем списке.

Запрос:

findall((Имя,Рост), данные(Имя,Возраст,Рост), Список).

42

Результат:

Список = [(игорь,189), (роман,182), (виктор,185), (антон,191), (николай,193)]

Теперь выполним те же запросы с предикатом bagof. Запрос:

bagof((Имя,Рост), данные(Имя,20,Рост), Список).

Результат не изменится:

Список = [(виктор,185), (антон,191)]

Запрос:

bagof((Имя,Рост), данные(Имя,Возраст,Рост), Список).

Результат:

Возраст = 16, Список = [(николай,193)]

Нажимаем Next:

Возраст = 17, Список = [(роман,182)]

Нажимаем Next:

Возраст = 18, Список = [(игорь,189)]

Нажимаем Next:

Возраст = 20, Список = [(виктор,185), (антон,191)]

То есть мы получили результат в виде набора частичных результатов для каждого значения неконкретизированной переменной Возраст, причём значения этой переменной идут в порядке возрастания, а не в порядке появления в таблице!

Предикат setof отличается от bagof тем, что сортирует каждый частичный результат и убирает дубликаты. То есть setof формирует множество уникальных значений, в то время какbagof формирует набор возможно повторяющихся значений!

Выполним последний запрос с предикатом setof:

setof((Имя,Рост), данные(Имя,Возраст,Рост), Список).

43

В выводе изменится только последний частичный результат– два спортсмена будут отсортированы по первому атрибуту – по атрибуту Имя.

Возраст = 20, Список = [(антон,191), (виктор,185)]

Базу данных можно сделать динамической, т. е. её можно будет изменять в процессе выполнения программы. Для этого добавим в начало программы указание:

:- dynamic данные/3.

Число 3 указывает на количество аргументов в предикате«данные» (так называемая арность предиката). После этого можно добавлять факты в базу данных с помощью предикатовasserta (в начало базы данных) и assertz (в конец базы данных).

Добавим, например, в запросе новую запись в таблицу:

asserta(данные(петр,24,178)),

и определим спортсмена/спортсменов минимального роста:

findall(Рост, данные(_,_,Рост), Список), min_list(Список, МинРост),

данные(Имя, Возраст, МинРост).

Результат:

Возраст = 24, Имя = петр, МинРост = 178,

Список = [178, 189, 182, 191, 185, 193]

Можно также извлекать значения из базы данных с помощью предиката retract. Для иллюстрации этой возможности рассмотрим следующий пример. Разработаем собственный датчик случайных чисел на основе линейного конгруэнтного метода. Суть метода заключается в вычислении последовательности случайных чисел X(1), X(2),…,X(n), где X(1) задаётся, а остальные значения вычисляются по формуле

X(n)=(A*X(n-1)+B) mod M.

Такой датчик генерирует случайные числа в диапазоне от 0 до M-1. Зададим A = 21, B = 57, M = 100. При этом последовательность случайных чисел будет включать все значения из диапазона 0–99.

Поскольку значение случайного числа вычисляется по рекуррентной формуле, при каждом вызове предиката нужно иметь доступ к предыдущему значению. Его удобно хранить в динамической базе данных.

44

% объявляем динамическую базу данных

:- dynamic num/1.

num(43).

%

начальное значение

rnd(X):-

%

автоматически запишется в базу

 

 

%извлекаем значение из базы в переменную S retract(num(S)),

%вычисляем значение нового случайного числа

X is (21*S+57) mod 100,

%и записываем его в базу

asserta(num(X)).

Используя написанный предикат, заполним случайными числами список из N элементов.

main(0,[]):-!. main(N,R):-

rnd(X), N1 is N-1, main(N1,R1), R=[X|R1].

Запрос: main(10,R).

Результат: R = [60, 17, 14, 51, 28, 45, 2, 99, 36, 13]

1.10. Логические задачи

В логических задачах на основе имеющейся информации необходимо получить таблицу соответствия. Разберём решение следующей задачи:

Задача 1. Коля, Боря, Вова и Юра заняли первые четыре места в спортивном соревновании. На вопрос, какие места они заняли, они честно ответили:

Коля не занял ни первое, ни четвертое место;

Боря занял второе место;

Вова не был последним.

Какое место занял каждый мальчик?

Решение легко получить логическими рассуждениями.

Из первого утверждения следует, что Коля мог занять только второе или третье место. Так как Боря занял второе место, то Коля – третье. Вова не был последним, значит, он занял первое место. Оставшееся четвертое место занял Юра.

Ответ: Вова занял первое место, Боря – второе, Коля – третье, Юра – четвертое.

45

Решение на Прологе.

Напишем сначала вспомогательные предикаты.

Предикат all_dist(список) возвращает true, если в списке нет повторяющихся элементов. Алгоритм работы предиката:

Представляем список в виде[H|T], где H – голова списка, а T – его хвост. Если в хвосте спискаT обнаруживается значение H, то предикат возвращает false и прекращает работу, в противном случае выполняется рекурсивный вызов для хвоста списка. Рекурсия завершается, когда список (аргумент предиката) станет пустым. В этом случае повторяющихся элементов в исходном списке нет.

all_dist([]). all_dist([H|T]):-

member(H,T),fail; not(member(H,T)),all_dist(T).

Предикат mem_list(список_переменных, список_значений) проверяет вхождение перечисленных переменных в список_значений. Список переменных представляется в виде [H|T], где H – голова списка, а T – его хвост. Сначала проверяется, что значение H входит в список_значений, а затем выполняется рекурсивный вызов предиката для хвоста списка. Если значение H не входит в список_значений, то выполняется предикат fail. Если же список_переменных становится пустым, то возвращается значение true.

mem_list([],_). mem_list([H|T],R):-

member(H,R),mem_list(T,R); fail.

Напишем теперь основной предикат. Места, занятые Колей, Борей, Вовой и Юрой, обозначим переменными A, B, C, D, тогда таблицу соответствия можно задать какRes = [(коля, A), (боря, B), (вова, C), (юра, D)]. Значения, которые могут принимать эти переменные, соберём в список

VList = [1,2,3,4]. Итак,

места(Res):-

Res = [(коля,A),(боря,B),(вова,C),(юра,D)], VList = [1,2,3,4],

mem_list([A,B,C,D], VList),

Теперь добавим условия.

Коля не занял ни первое, ни четвертое место:

A\=1, A\=4,

46

Боря занял второе место:

B=2,

Вова не был последним:

C\=4,

Необходимо также, чтобы значения всех переменных были разными:

all_dist([A,B,C,D]).

Вызовем предикат места(Res). Результат:

Res = [(коля,3), (боря,2), (вова,1), (юра,4)]

То есть Коля занял третье место, Боря – второе, Вова – первое, а Юра – четвертое.

SWI Prolog может представить результат в виде таблицы. Для этого в начале программы нужно поместить директивуuse_rendering(table, …) с объявлением заголовка таблицы:

:- use_rendering(table, [header(h('Имя','Место'))]).

В самой программе изменится только одна строчка:

Res = [h(коля, A), h(боря, B), h(вова, C), h(юра, D)],

Результат:

Задача 2. За столиком в кафе познакомились три девушки: Алёна, Светлана и Марина. Одна из них работает парикмахером в салоне красоты, другая – модельером в доме мод, третья – медсестрой в санатории. Также выяснилось, что парикмахер – самая молодая из трёх, Марина старше модельера, а Светлана младше Алёны. Определите профессии девушек.

Основа решения – таблица соответствия:

Res =[('Алена', P1, A1), ('Светлана', P2, A2), ('Марина', P3, A3)],

47

где P1, P2 и P3 – профессии девушек, A1, A2, A3 – возраст девушек. Поскольку реального возраста девушек мы не знаем, присвоим им условный возраст: самой младшей 1, средней 2, самой старшей 3.

Напишем вспомогательный предикатless(N1,P1,N2,P2), который возвращает true, если человек с атрибутами N1, P1 (имя и профессия первой девушки) моложе человека с атрибутами N2, P2 (имя и профессия второй девушки). При этом для каждого человека может быть известна только часть атрибутов.

less(N1,P1,N2,P2,R):- member(h(N1,P1,A1),R), member(h(N2,P2,A2),R), A1<A2.

В основном предикате используем также написанные ранее предикаты all_dist и mem_list.

solve(Res):-

Res =[('Алена',P1,A1), ('Светлана',P2,A2), ('Марина',P3,A3)],

List = [парикмахер, модельер, медсестра], mem_list([P1,P2,P3], List), all_dist([P1,P2,P3]), mem_list([A1,A2,A3],[1,2,3]), all_dist([A1,A2,A3]),

%парикмахер – самая молодая less(_,парикмахер,_,модельер,Res), less(_,парикмахер,_,медсестра,Res),

%Марина старше модельера

less(_,модельер,'Марина',_,Res), % Светлана младше Алёны less('Светлана',_,'Алена',_,Res).

Запрос: solve(_Res), print(_Res).

Результат:

['Алена',модельер,2, 'Светлана',парикмахер,1, 'Марина',медсестра,3].

Задача 3. Пять мальчиков играли во дворе в футбол и разбили мячом окно. На вопрос «Кто разбил окно?» были получены следующие ответы:

Ваня сказал: «Это или Паша, или Денис». Паша сказал: «Это сделал не я и не Вова».

48

Митя сказал: «Один из них говорит правду, а другой – нет».

АВова сказал: «Митя, ты ошибаешься».

Абабушка сидела на лавочке и всё видела . Она сказала, что только один мальчик сказал неправду, но не выдала того, кто разбил окно.

Кто же разбил окно?

Решение. Информацию, полученную от мальчиков, запишем в виде набора правил info(N,X), где N – имя автора высказывания, X – кто разбил окно.

Первые два высказывания – это просто.

info(ваня,X):-X=паша; X=денис. info(паша,X):-not(X=паша), not(X=вова).

Самое сложное – задать третье высказывание: верна информация Вани и неверна Паши или верна информация Паши и неверна – Вани.

info(митя,X) :-

(

info(ваня,X),not(info(паша,X)) );

(

not(info(ваня,X)),info(паша,X)).

Напомним, что в Прологе логическое И заменяется запятой, а логическое ИЛИ – точкой с запятой. Круглые скобки используются для группировки операндов, хотя в данном случае их можно убрать – выражение будет вычисляться правильно в силу приоритетов логических операций.

И последнее – высказывание Вовы:

info(вова,X) :- not(info(митя,X)).

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

_W=[ваня,паша,митя,вова],_L=[_W,денис],

Мальчика, который разбил окно, обозначим переменной X. Для каждого конкретного значения X получим список имён мальчиков, чьи высказывания по отношению к X оказались ложными (т. е. значение info(N,X) есть false). Для этого используем предикат bagof:

bagof(N, (member(N,_W), member(X,_L), not(info(N,X))), C),

Из найденных вариантов решением задачи являются только те случаи, когда список C состоит ровно из одного значения:

49

C=[_].

Результат:

C = [митя], – неправду сказал только Митя X = денис – а окно разбил Денис.

Задача 4. Три друга – Петр, Роман и Сергей учатся на математическом, физическом и химическом факультетах университета. Известно, что истинны следующие высказывания:

Если Петр – математик, то Сергей не физик. Если Роман не физик, то Петр – математик. Если Сергей не математик, то Роман – химик.

Определите специальности друзей.

Решение. Новое в этой задаче– высказывания-импликации. Заменим импликацию эквивалентным высказыванием по формуле: из A следует B тогда и только тогда, когда истинно выражение не A или B.

if A then B ~ not(A) or B.

Тогда высказывание «Если Петр – математик, то Сергей не физик» эквивалентно «Петр не математик или Сергей не физик», высказывание «Если Роман не физик, то Петр – математик» эквивалентно «Роман – физик или Петр – математик», а высказывание «Если Сергей не математик, то Роман – химик» эквивалентно высказыванию «Сергей – математик или Роман – химик».

Решение будем искать в виде списка структур:

Res=[('Петр',P), ('Роман',R), ('Сергей',S)],

где P, R, S – специальности Петра, Романа и Сергея соответственно. Сформулируем имеющиеся высказывания в виде отдельных предика-

тов:

v1(P, S):-P\='мат'; S\='физ'. v2(P, R):-R='физ'; P='мат'. v3(R, S):-S='мат'; R='хим'.

Главная программа будет иметь следующий вид:

solve(Res):- Prof=['мат','физ','хим'], mem_list([P,R,S],Prof), all_dist([P,R,S]),

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]