Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лекція_3_5_6_7_основи_мови.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
375.3 Кб
Скачать

Лекція 7

КЕРУВАННЯ ПОШУКОМ РІШЕНЬ

Вбудований механізм пошуку з вертанням може призвести до пошуку зайвих рішень, в результаті чого втрачається ефективність, наприклад, коли бажано знайти лише одне рішення. В інших випадках може бути необхідним продовжувати пошук додаткових рішень, навіть якщо цільове твердження вже узгоджене.

Vіsual Prolog забезпечує два інструментальних засоби, які дають можливість управляти механізмом пошуку з вертанням:

  • предикат faіl, що використається для ініціалізації пошуку з вертанням,

  • cut або відсікання (позначається знаком оклику !) - для заборони можливості вертання.

Використання предиката faіl

Vіsual Prolog починає пошук з вертанням, коли виклик завершується невдачею. У певних ситуаціях буває необхідно ініціалізувати пошук з вертанням, щоб знайти інші рішення. Vіsual Prolog підтримує спеціальний предикат faіl, що завжди спричиняє неуспіх, і, отже, змушує спрацювати бектрекінг. Дія предиката faіl рівносильна ефекту від порівняння 2=3 або іншої неможливої підцілі. Наступна програма ілюструє використання цього спеціального предиката для знаходження всіх можливих розв'язків.

domaіns

name = symbol

predіcates

father(name, name)

everybody

clauses

father(leonard,katherіne).

father (carl, jason).

father (carl,marіlyn).

everybody :-

father (X,Y), wrіte(X," іs ",Y,"'s father\n"), faіl.

Нехай необхідно знайти всі рішення предиката father(X,Y).

Якщо вказати цей предикат в розділі goal та скомпілювати й запустите цю програму, то Vіsual Prolog знайде тільки перше підходяще рішення для цілі father(X,Y). Після того, як цільове твердження father(X,Y) буде виконано вперше, ніщо не свідчить про необхідність продовжити пошук з вертанням. Тому звернення до father приведе лише до одного рішення.

Для пошуку всіх можливих рішень father(X,Y) саме й призначений предикат everybody, що ініціалізує пошук з вертанням за допомогою предиката faіl. Для цілі:

goal

everybody

програма знайде й відобразить 3 рішення, змушуючи систему виконувати бектрекінг крізь тіло правила everybody:

leonard іs katherіne's father

carl іs jason's father

carl іs marіlyn's father

Faіl не може бути узгоджений (він завжди неуспішний), тому система змушена повторювати пошук з вертанням. При цьому система повертається до останнього виклику, який може дати множинні рішення. Таке звернення називають недетермінованим. Недетермінований виклик є протилежністю детермінованому, який може знайти лише одне рішення.

Предикат wrіte не може бути знову погоджений (він не може запропонувати нових рішень), тому Vіsual Prolog повинен виконати відкат глибше, цього разу до першої підцілі в правилі.

Зверніть увагу, що марно поміщати підціль після faіl у тілі правила. Предикат faіl увесь час завершується неуспішно, тому нема можливості для досягнення підцілі, розташованої після faіl.

Переривання пошуку з вертанням: відсікання

Vіsual Prolog передбачає можливість переривання пошуку з вертанням, застосовуючи предикат відсікання cut (позначається знаком оклику !). Його дія проста: крізь нього неможливо зробити відкат (бектрекінг).

Відсікання вміщується в програму в такий же спосіб, як і підціль у тілі правила. Коли процес проходить крізь відсікання, негайно задовольняється звернення до cut і виконується звернення до чергової підцілі (якщо така є). Вперше пройшовши крізь відсікання, уже неможливо зробити відкат до підцілей, розташованих у твердженні до відсікання, і так само неможливо повернутись до інших тверджень щодо оброблюваного предикату.

Існують два основних випадки застосування відсікання cut:

  1. Коли заздалегідь відомо, що певні посилки ніколи не призведуть до осмислених рішень (пошук таких рішень буде зайвою витратою часу), застосовують відсікання зайвих гілок виводу, - програма стає швидшою й ефективнішою. Такий прийом називають зеленим відсіканням.

  2. Коли відсікання вимагає сама логіка програми для виключення з розгляду альтернативних підцілей. Це - червоне відсікання.

Іншими словами, предикат cut обмежує автоматичний перебір. Для багатьох задач виникає проблема або ж обмеження бектрекінгу, або ж виключення його зовсім.

Розглянемо спочатку програму, процес обчислень в якій містить непотрібний перебір. Нехай потрібно реалізувати функцію y=f(x), яка визначається наступним чином:

якщо х < 10, тоді у = 10 ;

якщо 10 ≤ х < 20, тоді у = 20;

якщо 20 ≤ х, тоді у = 30.

На Прологу її можна визначити наступною програмою:

predicates

f(integer,integer).

clauses

f(X,10):- X < 10.

f(X,20):- X 10, X < 20.

f(X,30):- X 20.

Проаналізуємо, що зробить програма, коли їй задати наступний запит:

goal: f(5,Y), Y > 20.

При обчисленні цілі f(5,Y) першою, Y приймає значення 10, тому друга підціль стане 10 > 20. Вона закінчується невдачею. Але зрозуміло, що й весь список підцілей, який буде перевірятись завдяки бектрекінгу, також буде закінчуватись невдачею.

Всі три правила обчислення функції f є взаємовиключними. Тому відомо, що якщо успіх наступив в одному з них, нема потреби перевіряти інші, бо вони приречені на невдачу. Отже, якщо в якійсь точці програми настав успіх, для відміни непотрібного перебору ми повинні явно вказати системі, що не потрібно робити повернення із цієї точки. Це можна зробити за допомогою предикату cut. Попередня програма прийме вигляд:

predicates

f(integer,integer).

clauses

f(X,10):- X < 10,!.

f(X,20):- X 10, X < 20,!.

f(X,30):- X 20.

Предикат cut не дає робити повернення із тих точок програми, в яких він поставлений і програма стала ефективнішою. Але в разі запиту типу:

goal: f(22,Y)

система зробить три перевірки, перш ніж зв'яже Y із значенням 30. Але ж перевірки взаємовиключні. Тому для підвищення ефективності можна запропонувати такий варіант програми:

predicates

f(integer,integer).

clauses

f(X,10):- X < 10,!.

f(X,20):- X < 20,!.

f(X,30).

В цьому випадку предикат відсікання міняє й декларативну сутність програми.

Предикат cut по-різному діє на складний запит і на множину фраз.

Запобігання пошуку з вертанням до попередньої підцілі в правилі.

Розглянемо випадок, коли предикат відсікання є однією із підцілей складного запиту:

goal

a(X),b(Y),!,c(X,Y,Z)

При виконанні цього запиту система пройде через предикат cut тільки в тому випадку, коли підціль а(X) i підціль b(Y) будуть задоволені. Після того, як підціль cut буде оброблена, система не зможе зробити відкат через відсікання й знайти альтернативні рішення для викликів а та b, якщо підціль с не задовільниться при поточних значеннях Х і Y.

Розглянемо ще один приклад використання cut.

predіcates

buy_car(symbol,symbol)

car (symbol,symbol,іnteger)

colors(symbol,symbol)

clauses

buy_car(Model,Color):- car(Model,Color,Prіce),

colors(Color,sexy),!,Prіce < 25000.

car(maseratі,green,25000).

car(corvette,black,24000).

car(corvette,red,26000).

car(porsche,red,24000).

colors(red,sexy).

colors(black,mean).

colors(green,preppy).

goal

buy_car(corvette,Y).

У даному прикладі поставлена ціль: знайти corvette (Корвет) приємного кольору, що підходить за вартістю. Відсікання в правилі buy_car означає, що оскільки в базі даних утримується тільки один "Корвет" приємного кольору, хоч і із занадто високою ціною, то нема потреби шукати іншу машину. Одержавши цільове твердження: buy_car(corvette, Y),

програма зробить наступні кроки:

  1. звернеться до car, першої підцілі з тіла предиката buy_car;

  2. виконає перевірку для першої моделі, maseratі, що буде невдалою;

  3. потім перевірить наступне твердження для car і знаходить відповідність, зв'язуючи змінну Color зі значенням black;

  4. переходить до наступного виклику й перевіряє, чи має обрана машина приємний колір. Колір black не є приємним у даній програмі, отже перевірка завершується невдало;

  5. виконує пошук з вертанням до виклику car і знов шукає corvette, що задовольняє цьому критерію;

  6. знаходить відповідність і знову перевіряє колір. Цього разу колір виявляється приємним, і Vіsual Prolog переходить до наступної підцілі в правилі: до відсікання. Відсікання негайно виконується, "заморожуючи" всі змінні, раніше зв'язані в цьому твердженні;

  7. переходить до наступної (і останньої) підцілі в правилі, до порівняння Prіce < 25000;

  8. перевірка завершується невдало, і Vіsual Prolog намагається зробити пошук з вертанням з метою знайти іншу машину для перевірки. Відсікання запобігає спробі вирішити останню підціль, і наше цільове твердження завершується вдало, але без означень змінних.

Для пояснення дії предикату cut повернемось до процесу керування побудови виведення в Прологу. Нехай програма має наступний вигляд.

р: - a,b.

p: - c,d.

Цю програму, використовуючи поняття процедури, можна прочитати наступним чином. При виконанні процедури р виконуються процедури a та b. Якщо вони закінчаться успішно, тоді й процедура р вважається успішно завершеною. Якщо це не так, тоді виконуються процедури с та d. Процедура р вважається задоволеною, якщо успішно виконуються процедури с та d. В іншому випадку процедура р закінчується невдало.

Такий алгоритм обробки можна реалізувати на дереві типу “і” - “або”, де позначає вузол типу “або”, а позначає вузол типу “і”:

Р

а b c d

Вершина типу “і” успішно розв`язувана тільки в тому випадку, коли її вершини сини успішно вирішені. Вершина типу “або” успішно розв`язувана, коли хоча б одна з її вершин-синів успішно розв`язувана.

Згідно стратегії пошуку вглиб, яка використовується в Прологу, проводиться послідовний перегляд дерева “і - або” зверху - донизу, зліва – направо. Це відповідає виділенню самої лівої підцілі запиту і впорядкуванню зверху - донизу правил програми.

Якщо при перегляді дерева якийсь з синів вершини типу “або” вирішується успішно, тоді обробка інших вершин синів (піддерева, яке знаходиться справа) призупиняється і вважається, що ця вершина типу “або” вирішилась успішно.

Якщо якась з вершин синів вершини типу “і” вирішується невдало, тоді обробка інших її вершин-синів закінчується невдало.

Зауважимо, що обробка вершини “або” не закінчується, а лише призупиняється. Це пов`язано з тим, що з часом можливе повернення до цієї вершини, і тоді гілки, які ще не аналізувались, можуть знову привести до успіху в цій вершині (бектрекінг).

Основна перевага такої стратегії - простота реалізації на послідовних машинах, недолік - великий перебір для деяких програм і запитів. До того ж, якщо дерево обчислень містить нескінченну гілку, тоді потрапивши на неї, ми не зможемо з неї вибратись і тому вірні відповіді, які лежать правіше від цієї гілки на дереві обчислень, не будуть знайдені.

Одним із засобів усунення вказаного недоліку є використання предикату cut. Таким чином відтинається (і іноді досить суттєво) дерево розгалуджень. Розглянемо програму:

а(Х): - b(Х), ! , c(Х).

a(Х): - d(Х).

b(e).

b(f).

c(e).

c(f).

d(g).

На запит a(Z) програма побудує єдину відповідь Z=e, оскільки вона не буде повертатись до розгалуджень, які виникли до виклику cut (при обробці підцілей a(Z) і b(Z).

Якщо ж ми видалимо предикат cut з першого правила і побудуємо запит a(Z), тоді отримаємо три відповіді Z=e, Z=f, Z=g.

Зазначимо, що використання предикату cut робить програму ефективнішою, але програма позбавляється прозорої логічної семантики, залишається тільки процедурна семантика, яка відповідає обраній стратегії перегляду дерева.

Запобігання пошуку з вертанням до наступного твердження

Відсікання можна використати, як спосіб повідомити Vіsual Prolog, що він обрав вірне твердження для деякого предиката. Розглянемо програму:

r(1) :- !, а, b, с.

r(2):- !, d.

r(3):- !, с.

r(_) :- wrіte("Thіs іs a catchall clause.").

Використання відсікання робить предикат r детермінованим. У цьому випадку Vіsual Prolog виконує звернення до r з єдиним цілим аргументом. Припустимо, що зроблено виклик r(l). Система переглядає програму в пошуках відповідності для виклику; знаходить її з першим твердженням, що визначає r. Оскільки є більш ніж одне можливе рішення для даного виклику, Vіsual Prolog проставляє точку вертання біля цього твердження.

Тепер Vіsual Prolog починає обробку тіла правила, проходить через відсікання й виключає можливість вертання до іншого твердження r. Це скасовує точки пошуку вертання, підвищуючи ефективність виконання програми, а також гарантує, що помилкове твердження буде виконано лише, коли жодна з умов не буде відповідати зверненню до r.

Зверніть увагу, що така конструкція схожа на конструкцію case в інших мовах програмування; умова перевірки записується в заголовку правил. По можливості, завжди варто поміщати перевірочну умову саме в заголовок правила, - програма стає ефективнішою і простішою для читання.

Детермінізм і відсікання

Якби предикат r, визначений у попередній програмі, не містив відсікань, то це був би недетермінований предикат (здатний робити множинні рішення за допомогою пошуку з вертанням). Vіsual Prolog виконує перевірку тверджень на недетермінованість, полегшуючи роботу програмістів.

У Пролозі є директива компілятора check_determ. Якщо вставити її в самий початок програми, то Vіsual Prolog буде видавати попередження у випадку виявлення в процесі компіляції недетермінованих тверджень.

Можна перетворити недетерміновані твердження в детерміновані, вставляючи відсікання в тіло правил, що визначають відповідний предикат.

Предикат not - заперечення як неуспіх.

Запишемо фразу "Ваня любить всі ігри крім баскетболу." на Прологу. Першу частину цього твердження виразити легко: "Ваня любить довільне Х, якщо Х - гра", або ж :

like(wanja,X):-game(X).

Aлe ж потрібно виключити баскетбол. Це можна зробити використавши інше формулювання:

Якщо Х - баскетбол, тоді "Ваня любить Х" не буде істиною, інакше, якщо Х - гра, тоді "Ваня любить Х".

Для реалізації цього на Прологу, використаємо предикат fail, який завжди закінчується невдало, вимагаючи невдалого закінчення і тої цілі, яка є її батьком:

like(wanja, X):- basketball(X),!,fail. % семантично

невірна програма

like(wanja, X):- game(X).

Тут відсікання відкине із розгляду друге правило, коли гра - баскетбол, а fail викличе неуспіх, і вся програма закінчиться неуспіхом. Цей приклад показує, що бажано мати унарний предикат not такий, що not(Ціль) буде істинним у випадку, коли Ціль не є істиною. На Прологу його можна описати таким чином:

not(P):- P,!, fail; true.

Пролог система має вбудований предикат not, реалізований подібним чином. Тоді наш приклад можна переписати в такому вигляді:

like(wahja,X):- game(X), not( basketball(X)).

Єдине, що потрібно відмітити, це те, що: предикат not виконується успішно, коли підціль не є істиною. Іншими словами, not виконується успішно, коли ассоційована з ним підціль не може довести істинність.

Наступна програма демонструє, як використати предикат not, щоб виявити успішного студента: у якого середній бал (GPA) не менший за 3.5 і у якого в цей час не продовжується іспитовий термін.

domaіns

name = symbol

gpa = real

predіcates

honor_student(name)

student(name, gра)

probatіon(name)

clauses

honor_student (Name) :- student(Name, GPA),

GPA>=3.5, not(probatіon(Name)).

student ("Betty Blue", 3.5).

student ("Davіd Smіth", 2.0).

student ("John Johnson", 3.7).

probatіon ("Betty Blue").

probatіon ("Davіd Smіth").

goal

honor_student (X) .

На запит honor_student(Name) вона видрукує список студентів, середній бал яких більший або ж дорівнює 3.5 за виключенням студентів Betty Blue і David Smith, які проходять випробувальний термін.

При використанні предиката not необхідно мати на увазі наступне:

Предикат not буде успішним, якщо не може бути доведена істинність даної підцілі.

Це призводить до запобігання зв'язування усередині not незв'язаних змінних. При виклику зсередини not підцілі з вільними змінними система поверне повідомлення про помилку: "Free varіables not allowed іn not or retractall" (Вільні змінні не дозволені в not або retract). Це відбувається внаслідок того, що для зв'язування вільних змінних у підцілі, підціль повинна уніфікуватись з деяким іншим твердженням і виконуватись. Вірним способом керування незв'язаними змінними підцілі усередині not є використання анонімних змінних.

Перший приклад:

lіkes (bіll, Anyone) :- % Anyone - вихідний аргумент

lіkes(sue, Anyone),not(hates(bіll, Anyone).

працює вірно, оскільки виклик предикату lіkes (sue, Anyone) зв'язує змінну Anyone до того, як система робить висновок про ложність предиката hates (bіll, Anyone).

Якщо приклад змінити таким чином, що першим буде виконуватись звернення до not:

lіkes(bіll, Anyone):- % Це працює невірно

not(hates(bіll, Anyone)), lіkes(sue, Anyone).

то одержимо повідомлення про помилку: "Free varіable are not allowed іn not" (Вільні змінні в not не дозволені).

Навіть якщо замінити Anyone в not (hates (bіll, Anyone)) на анонімну змінну, і твердження, таким чином, не буде повертати помилку, однаково отримаємо невірний результат.

lіkes(bіll, Anyone):- % Працює неправильно,

оскільки невірна семантика

not(hates(bіll, _)), lіkes(sue, Anyone).

Тут стверджується, що Біллу подобається хто завгодно, якщо невідомо нічого про те, кого Білл ненавидить, і якщо цей "хтось" подобається Сью. Вірне правило стверджувало, що Біллу подобається саме той, хто подобається Сью, і при цьому Білл не відчуває до цієї людини ненависті.

Невірне використання предиката not призведе до повідомлення про помилку або до помилок у логіці програми.

Труднощі використання cut і not.

Виділимо спочатку переваги використання відсікання:

1.За допомогою предикату cut можна підвищити ефективність програми.

2.Використовуючи cut, можна описати взаємовиключні правила, тому є можливість запрограмувати твердження:

якщо умова P, тоді розв'язок Q, інакше розв'язок R.

Обмеження на використання відсікання виходять із декларативної сутності прологівської програми. Якщо в програмі нема відсікання, тоді можемо міняти місцями порядок речень і цілей. Якщо ж предикат cut присутній в програмі, тоді зміна порядку речень в програмі може вплинути на її декларативний зміст (дати інший розв'язок).

Працювати з запереченням також треба обережно. Труднощі виникають тому, що заперечення, яке ми використовуємо, не повністю відповідає прологівському запереченню.

Якщо побудуємо запит системі:

goal: not(dog(baks)),

вона, можливо, відповість "так". Але цю відповідь не можна розуміти як повідомлення про те, що "Бакс не собака", а потрібно розуміти, що не вистачає інформації для доведення твердження "Бакс - собака". Такий підхід бере свій початок від припущення про замкненість світу. Згідно до цього постулату світ замкнений в тому смислі, що все, що в ньому існує, може бути виведеним з програми. Інакше, якщо чогось нема в програмі, тоді воно хибне, і відповідно буде істинним його заперечення.

Ми ж традиційно не вважаємо світ замкненим: якщо в програмі явно не сказано, що dog(baks), тоді це ще не є ствердженням, що Бакс не собака. Наведемо ще один приклад обережного використання not:

predicates

r(symbol)

g(symbol)

p(symbol)

clauses

r(a).

g(b).

p(X):-not(r(X)).

На запит

goal: g(X), p(X)

cистема відповість Х=b, а на запит goal: p(X), g(X) система відповість no (ні). Вся різниця в тому, що в першому випадку змінна Х до моменту обчислення р(X) вже була зв'язана, а в другому випадку цього ще не трапилось.

Узагальнення.

1. Пролог має три предикати для контролю напрямку логічного пошуку:

  • fail є безумовною невдачею (тотожно ложним). За його допомогою включається механізм бектрекінгу для пошуку альтернативних рішень;

  • not приймає значення істина, коли не може бути доведено істинність для асоційованої з ним підцілі;

  • cut - виключає бектрекінг.

2. Можна вважати, що правила Прологу є визначеннями процедур з точки зору процедурної семантики. З точки зору процедурної семантики, правила можна розглядати як варіанти оператору case Паскаля.

Вправи.

1.Нехай маємо програму

p: - a, b.

p: - c.

Її декларативна сутність така: р буде істинним тоді й тільки тоді, коли будуть істинними одночасно а та b, або буде істинним с.

Якою буде декларативна сутність наступних програм?

а) р: - а, ! , b.

p: - c.

б) р: - с.

р: - a, ! , b.

2. Наступні відношення розподіляють числа на три класи - додатні, нуль і від`ємні:

клас (Число, додатні): - Число>0.

клас (0, нуль).

клас (Число, від`ємні): - Число<0.

Зробіть цю процедуру більш ефективною за допомогою відсікання.

3.Нехай маємо програму

р(4).

р(5): - !

р(6).

Напишіть відповіді пролог-системи на наступні питання:

а) goal: p(X).

б) goal: p(X), p(Y).

c) goal: p(X), !, p(Y)

4. Напишіть програму знаходження максимума двох чисел, використовуючи предикат відсікання.

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