
- •Управление перебором
- •5. 1. Ограничение перебора
- •5. 1. 1. Эксперимент 1
- •5. 1. 2. Эксперимент 2
- •5. 2. Примеры, использующие отсечение
- •5. 2. 1. Вычисление максимума
- •5. 2. 2. Процедура проверки принадлежности списку, дающая единственное решение
- •5. 2. 3. Добавление элемента к списку, если он в нем отсутствует (добавление без дублирования)
- •5. 2. 4. Задача классификации объектов
- •5. 3. Отрицание как неуспех
- •5. 4. Трудности с отсечением и отрицанием
- •Упражнения
5. 2. 4. Задача классификации объектов
Предположим, что у нас есть база данных, содержащая результаты теннисных партий, сыгранных членами некоторого клуба. Подбор пар противников для каждой партия не подчинялся какой-либо системе, просто каждый игрок встречался с несколькими противниками. Результаты представлены в программе в виде фактов, таких как
победил( том, джон). победил( энн, том). победил( пат, джим).
Мы хотим определить
отношение класс( Игрок, Категория)
которое распределяет игроков по категориям. У нас будет три категории:
победитель - любой игрок, победивший во всех сыгранных им играх
боец - любой игрок, в некоторых играх победивший, а в некоторых проигравший
спортсмен - любой игрок, проигравший во всех сыгранных им партиях
Например, если в нашем распоряжении есть лишь приведенные выше результаты, то ясно, что Энн и Пат - победители. Том - боец и Джим - спортсмен.
Легко сформулировать правило для бойца:
Х - боец, если существует некоторый Y, такой, что Х победил Y, и существует некоторый Z, такой, что Z победил X.
Теперь правило для победителя:
Х - победитель, если X победил некоторого Y и Х не был побежден никем.
Эта формулировка содержит отрицание "не", которое нельзя впрямую выразить при помощи тех возможностей Пролога, которыми мы располагаем к настоящему моменту. Поэтому оказывается, что формулировка отношения победитель должна быть более хитрой. Та же проблема возникает и при формулировке правил для отношения спортсмен. Эту проблему можно обойти, объединив определения отношений победитель и боец и использовав связку "иначе". Вот такая формулировка:
Если Х победил кого-либо и Х был кем-то побежден, то Х - боец, иначе, если Х победил кого-либо, то Х - победитель, иначе, если Х был кем-то побежден, то Х - спортсмен.
Такую формулировку можно сразу перевести на Пролог. Взаимные исключения трех альтернативных категорий выражаются при помощи отсечений:
класс( X, боец) :- победил( X, _ ), победил( _, X), !.
класс( X, победитель) :- победил( X, _ ), !.
класс( X, спортсмен) :- победил( _, X).
Заметьте, что использование отсечения в предложении для категории победитель не обязательно, что связано с особенностями наших трех классов.
5. 3. Отрицание как неуспех
"Мэри любит всех животных, кроме змей". Как выразить это на Прологе? Одну часть этого утверждения выразить легко: "Мэри любит всякого X, если Х - животное". На Прологе это записывается так:
любит( мэри, X) :- животное ( X).
Но нужно исключить змей. Это можно сделать, использовав другую формулировку:
Если Х - змея, то "Мэри любит X" - не есть истина, иначе, если Х - животное, то Мэри любит X.
Сказать на Прологе, что что-то не есть истина, можно при помощи специальной цели fail (неуспех), которая всегда терпит неудачу, заставляя потерпеть неудачу и ту цель, которая является ее родителем. Вышеуказанная формулировка, переведенная на Пролог с использованием fail, выглядит так:
любит( мэри, X) :- змея( X), !, fail.
любит( Мэри, X) :- животное ( X).
Здесь первое правило позаботится о змеях: если Х - змея, то отсечение предотвратит перебор (исключая таким образом второе правило из рассмотрения), а fail вызовет неуспех. Эти два предложения можно более компактно записать в виде одного:
любит( мэри, X) :- змея( X), !, fail; животное ( X).
Ту же идею можно использовать для определения отношения
различны( X, Y)
которое выполняется, если Х и Y не совпадают. При этом, однако, мы должны быть точными, потому что "различны" можно понимать по-разному:
Х и Y не совпадают буквально;
Х и Y не сопоставимы;
значения арифметических выражений Х и Y не равны.
Давайте считать в данном случае, что Х и Y различны, если они не сопоставимы. Вот способ выразить это на Прологе:
Если Х и Y сопоставимы, то цель различны( X, Y) терпит неуспех иначе цель различны( X, Y) успешна.
Мы снова используем сочетание отсечения и fail:
различны( X, X) :- !, fail.
различны( X, Y).
То же самое можно записать и в виде одного предложения:
различны( X, Y) :- Х = Y, !, fail; true.
Здесь true - цель, которая всегда успешна.
Эти примеры показывают, что полезно иметь унарный предикат "not" (не), такой, что
nоt( Цель)
истинна, если Цель не истинна. Определим теперь отношение not следующим образом:
Если Цель успешна, то not( Цель) неуспешна, иначе not( Цель) успешна.
Это определение может быть записано на Прологе так:
not( Р) :- P, !, fail; true.
Начиная с этого момента мы будем предполагать, что not - это встроенная прологовская процедура, которая ведет себя так, как это только что было определено. Будем также предполагать, что оператор not определен как префиксный, так что цель
not( змея( X) )
можно записывать и как
not змея( X)
Многие версии Пролога поддерживают такую запись. Если же приходится иметь дело с версией, в которой нет встроенного оператора not, его всегда можно определить самим.
Следует заметить, что not, как он здесь определен с использованием неуспеха, не полностью соответствует отрицанию в математической логике. Эта разница может породить неожиданности в поведении программы, если оператором not пользоваться небрежно. Этот вопрос будет рассмотрен позже.
Тем не менее not - полезное средство, и его часто можно с выгодой применять вместо отсечения. Наши два примера можно переписать с not:
любит( мэри, X) :- животное ( X), not змея( X).
различны( X, Y) :- not( Х = Y).
Это, конечно, выглядит лучше, нежели наши прежние формулировки. Вид предложений стал более естественным, и программу стало легче читать.
Нашу программу теннисной классификации из предыдущего раздела можно переписать с использованием not так, чтобы ее вид был ближе к исходным определениям наших трех категорий:
класс( X, боец) :- победил( X, _ ), победил( _, X).
класс( X, победитель) :- победил( X, _ ), not победил( _, X).
класс( X, спортсмен) :- not победил( X, _ ).