
- •3. Механизм автоматического поиска решений на Прологе
- •3.1 Рассмотрим поиск решений на примерах
- •3.2.1 Задача о холостяке
- •3.3 Задача о Ханойской башне
- •4. Важные особенности программ на Прологе
- •4.1.4 Вот пример программы на Прологе, которая работать не будет:
- •4.1.5 Ещё один пример программы, не работающей на Прологе.
- •Замечание
3. Механизм автоматического поиска решений на Прологе
При попытке согласования целевого утверждения Пролог выбирает первое из тех утверждений, голова которых сопоставима с целевым утверждением.
Если удастся согласовать и тело утверждения, то цель успешна. Если нет, то Пролог переходит к следующему утверждению, голова которого сопоставима с целевым утверждением, и так далее до тех пор, пока цель не будет успешна или не будет выяснено, что она не согласуется с базой данных.
3.1 Рассмотрим поиск решений на примерах
3.1.1 Решим на Прологе такую задачу.
Боря любит любую еду с хорошим вкусом.
Пусть у нас есть две еды – пиво и пирожок. И пусть, также у пирожка будет вкус хороший, а у пива – так называемый, bad.
Программа получается такой.
еда(пиво).
еда(пирожок).
имеет_вкус(пирожок,хороший).
имеет_вкус(пиво,bad).
любит(боря,X):-
еда(X),
имеет_вкус(X,хороший).
Сделаем запрос, чего же съест Боря?
?- любит(боря,What).
What = пирожок
Yes
Чтобы разобрать работу Пролога по нахождению решения, воспользуемся встроенным предикатом trace
?- trace.
true.
Снова поставим перед Прологом ту же цель и посмотрим, как он ищет решение.
[trace] ?- любит(боря,What).
Call: (6) любит(боря, _G284) ? creep
Call: (7) еда(_G284) ? creep
Exit: (7) еда(пиво) ? creep
Call: (7) имеет_вкус(пиво, хороший) ? creep
Fail: (7) имеет_вкус(пиво, хороший) ? creep
Поскольку предикат имеет_вкус(пиво, хороший) оказался Fail, происходит возврат к предикату еда и выбор нового факта, что в режиме [trace] указывается словом Redo
Redo: (7) еда(_G284) ? creep
Exit: (7) еда(пирожок) ? creep
Call: (7) имеет_вкус(пирожок, хороший) ? creep
Exit: (7) имеет_вкус(пирожок, хороший) ? creep
Exit: (6) любит(боря, пирожок) ? creep
What = пирожок ;
Fail: (7) имеет_вкус(пирожок, хороший) ? creep
Fail: (7) еда(_G284) ? creep
Fail: (6) любит(боря, _G284) ? creep
No
Теперь можно этот режим отладки отключить
[trace] ?- notrace.
true.
Процесс согласования целевого утверждения путем прямого продвижения по программе называется прямой трассировкой (forward tracking).
Пролог производит доказательство конъюнкции целевых утверждений слева направо.
При этом может оказаться, что встретится утверждение, согласовать которое не удается. Если такое случается, то происходит смещение обратно, т.е. влево до тех пор, пока не будет найдено утверждение, которое может быть вновь согласовано, но теперь уже с другими параметрами.
Если слева нет таких утверждений, то конъюнкцию целевых утверждений согласовать нельзя и цель будет неуспешной.
Однако, если предшествующее целевое утверждение может быть согласовано вновь, Пролог возобновляет процесс доказательства целевых утверждений, начиная со следующего справа целевого утверждения.
Описанный процесс смещения влево для повторного согласования целевого утверждения и возвращения вправо носит название поиск с возвратом (backtracking).
3.1.2 Пример на сложный запрос цели.
Наша задача узнать, какие игроки будут играть друг с другом в детском шахматном турнире.
Дополнительное условие – играют только девятилетки, а захотели играть четверо: Петя (9 лет), Паша(10), Даша(9) и Саша(9).
Поступим так. Зададим простую «фактическую» БД, а целевой запрос напишем с конкретным условием поиска.
игрок_в_шахматы(петя,9).
игрок_в_шахматы(паша,10).
игрок_в_шахматы(даша,9).
игрок_в_шахматы(саша,9).
Запрос будет состоять из нескольких предикатов, каждый из которых мы будем вводить на новой строке, разделяя их запятой и Enter’ом
?- игрок_в_шахматы(Person1,9),
| игрок_в_шахматы(Person2,9),
| Person1 \= Person2.
Person1 = петя
P
erson2
= даша ;
P
erson1
= петя
Person2 = саша ;
Person1 = даша
Person2 = петя ;
P
erson1
= даша
Person2 = саша ;
Person1 = саша
Person2 = петя ;
Person1 = саша
Person2 = даша ;
No
?-
Чтобы заставить Пролог искать следующее решение, надо вводить символ «;».
В SWI-Prolog-Editor’е для продолжения можно просто нажать Enter
Последнее No означает, что больше решений нет.
Обратите внимание, что решения повторяются. Это происходит потому, что в нашей программе не запрограммировано запоминание уже найденных решений, а сам Пролог этого не делает.
3.1.3 Вот задача, где встречаются факт и правило одинакового имени и арности.
Что она решает - придумайте сами.
любит(федя,живопись).
любит(федя,книги).
любит(федя,петь).
любит(ваня,петь).
покупает_книги(ваня).
читает_книги(ваня).
любит(Z,книги):-
покупает_книги(Z),
читает_книги(Z).
При компиляции этой программы (consult) возможно получить warning о том, что Пролог находит некую раздвоенность в использовании предиката любит, что не влияет на правильность компиляции тела программы.
Задеём цель.
?- любит(X,петь),любит(X,книги).
X = федя ;
X = ваня ;
No
Теперь посмотрим действие этой «неопределённости» в работе с помощью trace
[trace] 35 ?- любит(X,петь),любит(X,книги).
Call: (8) любит(_G513, петь) ? creep
Exit: (8) любит(федя, петь) ? creep
Call: (8) любит(федя, книги) ? creep
Exit: (8) любит(федя, книги) ? creep
X = федя ;
Redo: (8) любит(федя, книги) ? creep
Все факты отработали. Теперь происходит переход к правилу, совпадающему с заголовком цели.
Call: (9) покупает_книги(федя) ? creep
Fail: (9) покупает_книги(федя) ? creep
Fail: (8) любит(федя, книги) ? creep !!!!!!!!!
Получается, что правило не подходит. Надо снова подбирать факты.
Redo: (8) любит(_G513, петь) ? creep
Exit: (8) любит(ваня, петь) ? creep
Call: (8) любит(ваня, книги) ? creep
Call: (9) покупает_книги(ваня) ? creep
Exit: (9) покупает_книги(ваня) ? creep
Call: (9) читает_книги(ваня) ? creep
Exit: (9) читает_книги(ваня) ? creep
Exit: (8) любит(ваня, книги) ? creep
X = ваня ;
Redo: (8) любит(_G513, петь) ? creep
No
Итак. Первое решение берется только из фактов, второе – одного факта и выполнения правила.
3.2 Рассмотрим задачи с использованием отрицания