Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Metodich.doc
Скачиваний:
0
Добавлен:
06.12.2018
Размер:
348.16 Кб
Скачать

4. Использование механизма отсечения для оптимизации поиска решения в программах на Прологе.

Возврат и отсечение.

В лабораторной работе №1 рассматривался процесс согласования целей с возвратом. Например, для предиката "принадлежит", определяющего принадлежность элемента списку:

member(X,[X|_]).

member(X,[_|Tail]): - member(X,Tail).

целевой предикат:

? - member(a,[a,b,a,c,a,d,a])

может быть согласован пятью различными способами.

Иногда требуется найти только одно решение, если оно существует и затем отбросить остальные возможные решения. Такое отбрасывание оставшихся решений может быть реализовано с помощью операции "отсечения".

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

- программа выполняется быстрее, т.к. не тратится время на попытки найти новые сопоставления для целей, о которых заранее известно, что они не внесут более ничего нового в решение.

- программа может занимать меньше места в памяти ЭВМ (не за поминаются точки возврата).

Синтаксически "отсечение" в правиле описывается предикатом "!" не имеющим аргументов. Как целевое утверждение этот предикат всегда согласуется с базой данных. При этом он изменяет процесс последующего возврата, так что маркеры некоторых целей становятся не доступными. Рассмотрим пример: Имеется база данных на Прологе, содержащая информацию о том кто какие книги взял в библиотеке и когда они должны быть возвращены. Одним из вопросов может быть: какие виды услуг доступны каждому из читателей. При этом основные услуги типа: пользование каталогом и справочным бюро, должны быть доступны любому читателю. А дополнительные услуги: пользование абонементом и получение книг из другой библиотеки - только тем, кто не имеет книг на руках.

Фрагмент программы выглядит следующим образом:

основные_услуги (пользование_катологом).

основные_услуги (получение_справок).

дополнительные_услуги (абонемент).

дополнительные_услуги (межбиблиотечный_абонемент).

общие_услуги (Х): -

основные_услуги(Х).

общие_услуги(Х): -

дополнительные_услуги (Х).

книга_не_возвращена ('С.Уотзер',книга 10089).

книга_не_возвращена ('А.Джонс',книга 29907).

.........................................

читатель ('А.Джонс').

читатель ('В.Метеск').

.........................................

услуги (Читатель,Вид_услуг): -

книга_не_возвращена (Читатель,Книга),

!,

основные_услуги (Вид_услуг).

услуги (Читатель,Вид_услуг): -

общие_услуги (Вид_услуг).

? – читатель (Х),услуги (Х,У).

Начав поиск ответа, Пролог выберет первого читателя: А.Джонс. Для того, чтобы определить, какие услуги доступны ему, Пролог воспользуется первым утверждением для предиката "услуги". Это приведет к появлению нового целевого утверждения - "книга_не_возвращена". После небольшого поиска среди фактов книга_не_возвращена будет обнаружен факт о первой не возращенной А.Джонсом в срок книги. Следующее целевое утверждение - "!". Эта цель автоматически согласуется с базой данных, и в результате этого в системе закрепляются все решения, принятые с момента выбора первого утверждения "услуги".

Когда в программе встречается "!", то оно отсекает путь, представляющий цепочку выполненных доказательств таким образом, что следующая цель соединяется непосредственно с исходной. Т.е. цель "основные_услуги" соединяется с целью "услуги" (первый предикат).

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

Таким образом, Пролог:

- не будет искать альтернативные решения для "книга_не_возвращена ", так как одна такая книга уже найдена;

- не будет осуществлять переход на второе утверждение предиката "услуги".

Можно рассматривать символ "!" как некий разделитель (забор), отделяющий целевые утверждения. Пусть, например, имеется конъюнкция целей:

foo: - a,b.c.!,d,e,f .

Пролог без каких либо ограничений может выполнять возвраты среди целей а, в, с. После того как Пролог "перешагнет" "забор" ! будут осуществляться возвраты только между d, e, f. При этом возможна неоднократная согласованность конъюнкции целиком. Однако, если произойдет неудача при согласовании целевого утверждения d, что вызовет "перешагивание через забор" справа налево, то новых попыток согласовать цель "c" не будет и предикат foo - потерпит неудачу.

Рассмотрим различные случаи использования отсечения.

Общие случаи использования отсечения.

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

Второй случай относится к ситуации, когда необходимо указать Прологу, что необходимо немедленно прекратить доказательство согласованности конкретного целевого утверждения, не пытаясь найти для него альтернативные решения. Это достигается сочетанием предикатов ! и fail.

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

Рассмотрим более подробно все случаи.

1.Подтверждение правильности выбора. Часто при программировании на Прологе возникают ситуации, когда для описания одного предиката используется несколько утверждений. Одно утверждение используется для аргументов одного вида, другое – для другого образца структур. В том случае, если неизвестно, какого вида аргументы будут использоваться, задаются правила для аргументов, определенных типов, а в конце задается правило - "ловушка" для всех остальных правил. Рассмотрим пример, демонстрирующий этот случай. Допустим, требуется вычислить сумму чисел от 1 до N. С этой цель определим следующие предикаты:

сумма(1,1): - !.

сумма(N,R):- N1=N-1, сумма(N1,R1), R=R1+N.

В этом фрагменте организована обработка двух случаев: первый аргумент равен 1; первый аргумент не равен 1. Если цель: ?-сумма (1,Х), то применимо правило 1 и правило 2. Для того, чтобы указать Прологу, что в этом случае не надо использовать правило 2, вводится предикат !.

Преобразуем программу таким образом, чтобы она выполнялась в том случае, если первый аргумент 0 или отрицательное число:

сумма(N,1): - N<=1,!.

сумма(N,R): - N1=N-1, сумма(N1,R1), R=R1+N.

Использование механизма отсечения для указания Прологу, что он выбрал правильное правило, можно заменить использованием встроенного предиката "not". Переписав программы с использованием предиката "not" получаем:

сумма(1,1).

сумма(N,R): - not(N=1),

N1=N-1,

сумма(N1,R1),

R=R1+N.

сумма(N,1): - N<=1.

сумма(N,R): - not(N<=1),

N1=N-1,

cумма(N1,R1),

R=R1+N.

Заметим, что использование предиката "not" вместо "!" свойственно для хорошего стиля программирования. Это связано с тем, что программы, содержащие отсечения более трудны для чтения. Однако употребление "not" приводит к потере эффективности.

Выше был рассмотрен предикат сцепления, который имеет вид:

append([],L2,L2).

append([X|Tail1],L2,[X|Tail3]): -

append(Tail1,L1,Tail3).

Что бы повысить эффективность программы при доказательстве цели append([],[a,b,c,d],X), Пролог обязан просмотреть правило 2. Для повышения эффективности предлагается перейти к следующему варианту:

append([],L2,L2):- !.

append([X|Tail1],L2,[X|Tail3]):-

append(Tail1,L2,Tail3).

2.Комбинация отсечение - fail. В этом случае используется конъюнкция "!" и fail. Встроенный предикат fail не имеет аргументов и доказательство его согласованности всегда заканчивается неудачей, что приводит к включению механизма "возврата".

Рассмотрим пример, демонстрирующий этот случай. Предположим, требуется определить среднего налогоплательщика. Иностранец не является средним налогоплательщиком. Не является средним налогоплательщиком также человек, у которого есть супруга, которая получает заработок больше некоторого порога. Фрагмент программы будет иметь вид:

средний_налогоплательщик(Х):-иностранец(Х),!,fail.

средний_налогоплательщик(Х):-

супруга(Х,У),доход(У,Доход),Доход>3000,!,fail.

средний_налогоплательщик(Х):-доход(Х,Доход),

Доход>2000, Доход<20000.

доход(Х,У):-получаемое_пособие(Х,Р),

Р<5000,!,fail.

доход(Х,У):-жалование(Х,Z),

доход_от_капиталовложений(X,W),

Y=Z+W .

доход_от_капиталовложений(X,Y):-...

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

средний_налогоплательщик(Х):-иностранец(Х),fail.

средний_налогоплательщик(Х):- ...

..............................

? - средний_налогоплательщик(a).

То при истинности предиката иностранец(а) предикат fail инициировал бы возврат и переход на правило 2 "средний_налогоплательщик". Возможно при применении второго правила ответ бы был "да". Для того, чтобы остановить поиск альтернатив в данном случае, необходимо отсечь сделанный выбор правила, прежде, чем будет выполнен предикат fail.

Далее, в определении предиката "доход" указано, что если человек получает пособие, сумма которого меньше некоторого порога, то независимо от других обстоятельств мы будем рассматривать его как вовсе не имеющего дохода.

Вариант программы с предикатом "not" имеет вид:

средний_налогоплательщик(Х) :-

not(иностранец(Х)),

not((супруга(Х,У),доход(У,Доход),Доход>3000)),

доход(Х,доход1),...

Заметим, конъюнкция целей заключена в двойные скобки, для того что бы показать, что запятые разделяют цели в конъюнкции, а не аргументы предиката "not".

3.Завершение операции "порождения и проверки". Общая модель работы программы следующая: имеется последовательность целей, которые могут быть согласованы множеством различных способов и которые порождают множество решений при возврате. Кроме того, имеются цели, проверяющие приемлемость порожденных решений для последующего использования. "Генератор"- это целевые утверждения, которые порождают всевозможные альтернативы, "контролеры"- это целевые утверждения, которые проверяют пригодность решений. Рассмотрим пример описания на Прологе операции деления целых чисел.

разделить(N1,N2,Результат):-

целое_число(Результат),

произведение1=Результат*N2,

произведение2=(Результат+1)*N2,

произведение1<=N1,

произведение2>N1,!.

Предикат "целое_число" порождает значение Результат, которое является результатом "деления" N1 на N2. Например, 27:6=4, т.к.4*6<=27 и 5*6>27.

Предикат "целое_число" - генератор, остальные предикаты относятся к типу "контролер". Так как существует одно такое число, которое является результатом деления, то, поставив отсечение в конце, мы прекратим генерацию.

Проблемы связанные с отсечением.

Перед написанием программы, необходимо детально изучить, как именно она будет использоваться. Для примера рассмотрим предикат “сцепление”:

append ([],X,X):-!.

append ([A|B],C,[A,D]):-append(B,C,D).

При доказательстве целей типа:

?-append([a,b,c],[d,e],Х) или

?-append([a,b,c],X,Y) использование отсечения уместно.

При доказательстве цели append(X,Y,[a,b,c]) будет получен единственный ответ Х=[],Y=[a,b,c].

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

число_родителей (адам,0):-!.

число_родителей(ева,0):-!.

число_родителей (Х,2).

Ставим цели и получаем ответы:

? - число_родителей(ева,Х). Имеем ответ: Х=0.

? - число_родителей(джон,Х). Имеем ответ: Х=2.

? - число_родителей(ева,2). Имеем ответ: да.

Внесем изменения в программу:

число_родителей(адам,N):-!,N=0.

число_родителей(ева,N):-!,N=0.

число_родителей(Х,2).

Она не работает, если стоит цель число_родителей(Х,У). Для этой цели удобнее сделать следующее описание:

число_родителей(адам,0).

число_родителей(ева,0).

число_родителей(Х,2):-Х<>адам, Х<>ева.

Таким образом, получаем правило: надежное использование "!" возможно лишь в случае, когда известно как правила будут использоваться. Если характер использования правил меняется, то необходимо пересмотреть все случаи употребления "!".

Упражнение: Внести изменения в программы из раздела №3, с целью их более эффективной работы. Проверить работу программ для разных целей.

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