Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
AlgStr / Библиотека / ЛЕКЦИИ / POSIBNIK / ПРОГР НА ПРОЛОГЕ.doc
Скачиваний:
42
Добавлен:
23.03.2015
Размер:
669.7 Кб
Скачать

5.9. Метод аналізу станів (ас)

Задача. Скласти процедуру, що перевіряє входження об'єкта в список:

належить(об'єкт, список).

Якщо об'єкт знаходиться у голові списку – розв’язок може бути знайдений негайно. Перевірку цього випадку можна виразити за допомогою факту

належить(Об'єкт, [Об’єкт | Список]).

А тепер перевіримо входження цього об'єкта до хвіста списку:

належить(Об'єкт, [ H | T]): -

належить(Об'єкт,T). % Результат буде залежати від перевірки

% на входження об'єкта в хвіст списку

Інших випадків бути не може.

Розглянемо схему знаходження даного розв’язку. Спочатку встановлюємо, що існує лише дві можливості для даного об'єкта й списку:

а) об'єкт знаходиться в голові списку;

б) об'єкт не знаходиться в голові списку.

Потім ми вчимося розпізнавати ці випадки. Це можна зробити за допомогою зіставлення зразків аргументів:

а) Об'єкт і [Объект | T] – у межах одного твердження будь-яка змінна в даний момент може означати тільки теж саме;

б) Об'єкт і [Н | Т].

Нарешті ми визначаємо, що потрібно робити в кожному з цих випадків:

а) у першому випадку ціль узгоджується негайно (факт);

б) у другому випадку розв’язок знаходиться за допомогою рекурсивного звертання для пошуку об'єкта в хвості списку.

Ці кроки складають три етапи методу аналізу станів. Даний метод являє собою технологію розробки процедури, що встановлює зміст предиката. Метод складається з таких кроків:

1. Виявлення для процедури категорії (або стани) вхідних аргументів. Звичайно для кожного стану процедура буде мати одне твердження.

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

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

а) базовий стан - розв’язок в цьому стані знаходиться без рекурсивного виклику (тривіальний випадок рекурсії);

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

4. Установлення порядку розташування тверджень у процедурі. Першими слід розташувати твердження, що належать до базового стану. Часто ними є твердження-пастки. Вони визначають завершення процесу, коли ніякі інші стани неприйнятні.

5. Установлення, чи є стани взаємовиключними. Якщо визначення предиката передбачає видачу єдиного результату, слід упевненитися, що кожен можливий вхід дає відмову тільки в одному стані, який необхідно виявити. У противному разі при відкотах ПРОЛОГ може дати неправильні результати.

Проведемо останні два етапи для розробленої нами процедури “належить”. Для цього слід зробити таке.

Порядок розташування тверджень повинний бути таким, як у нас, а стани –взаємовиключними: об'єкт знаходиться або в голові, або у хвості списку. Одержимо розв’язок:

належить(Об'єкт, [Объект | _]).

належить(Об'єкт, [ _ | T]): -

належить(Об'єкт,T).

Деякі аргументи у твердженні змінено на анонімну змінну, тому що вони зустрічалися в них тільки один раз.

Цей предикат можна застосовувати, наприклад для визначення загального елемента двох списків:

загальний_ елемент(Список1, Список2, Елемент):-

належить(Елемент, Список1),

належить(Елемент, Список2).

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

Розглянемо кілька прикладів застосування даного методу.

Приклад 1. Конкатенація двох списків.

Розробити процедуру, що виконує конкатенацію двох списків

conc(Список1, Список2, Об'єднання_двох_списків)

Етапи:

1. Виділення двох станів:

а) Список1 – порожній;

б) Список1 – непорожній.

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

а) [ ];

б) [H | T].

3. Реалізація поводження процедури в цих станах у такий спосіб:

а) фактом

conc([ ], L, L).

% Якщо перший список порожній, то результатом

% конкатенації буде другий список

б) правилом

conc([H | T], L1, [H | L2]):-

conc(T, L1, L2). % Якщо перший список не порожній, то

% першим елементом результату буде перший елемент

% першого списку, а хвіст результату буде отриманий

% конкатенацією хвоста першого списку з другим

% списком (за допомогою рекурсивного виклику) .

4. Порядок тверджень у процедурі може бути довільним.

5. Твердження взаємовиключні: вигляд першого аргументу в першому й другому твердженні не допускають один одного.

У результаті отримаємо такий розв’язок:

conc([ ], L, L).

conc([H | T], L1, [H | L2]):-

conc(T, L1, L2).

Приклад 2. Вилучення елемента зі списку.

Розробити процедуру, що виконує вилучення елемента зі списку:

вилучити(Елемент, Вихідний_список, Результат)

Етапи:

1. Вигляділення двох станів:

а) потрібний елемент знаходиться на першому місці в списку;

б) потрібний елемент не знаходиться на першому місці в списку;

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

а) Елемент і [Елемент | Хвіст];

б) Елемент і [ H | Хвіст];

  • Реалізація поводження процедури в цих станах у такий спосіб:

а) фактом

вилучити(Елемент, [Елемент | Хвіст], Хвіст).

% Якщо Елемент знаходиться на першому місці у вихідному

% списку, то результатом буде хвіст вихідного списку без

% першого елемента.

б) правилом

вилучити(Елемент, [H | Хвіст], [ H | Список]):-

вилучити(Елемент, Хвіст, Список).

% Якщо Елемент не знаходиться на першому місці у

% вихідному списку, то перший елемент ( H ) переходить у

% результат, а залишок ( Список) знаходиться за допомогою

% рекурсивного виклику (вилученням Елемента з Хвоста).

4. Упорядкування тверджень у процедурі повинний таким чином: спочатку твердження для базового стану, а потім – для рекурсивного.

5. Перевірка взаємовиключності тверджень: вид першого аргументу в першому і другому твердженнях не допускають один одного.

У результаті одержимо розв’язок:

вилучити(Елемент, [Елемент | Хвіст], Хвіст).

вилучити(Елемент, [H | Хвіст], [H | Список]):-

вилучити(Елемент, Хвіст, Список).

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

Приклад 3. Вибір пари.

Розробити процедуру, що виконує генерацію списків пар, де перший елемент належить першому списку, а другий – другому списку:

Вибір_пари(Жінки, Чоловіки, Пари)

Етапи:

1. Вигляділення двох станів:

а) списки порожні;

б) списки непорожні;

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

а) [ ];

б) [H | Хвіст];

3) Реалізація поводження процедури в цих станах в такий спосіб:

а) фактом

вибір_пари([ ], [ ], [ ]).

% Якщо списки порожні, то й результатом буде порожній

% список пар

б) правилом

вибір_пари([H | Хвіст], Чоловіки, [пари(H,Чоловік) | Пари]):-

вилучити(Чоловік, Чоловіки, Залишок),

вибір_пари(Хвіст, Залишок, Пари).

% Якщо список жінок не порожній, то методом прогресуючої

% підстановки формуємо список пар, поміщуючи на перше

% місце пари структуру пари(H, Чоловік), де H – перша жінка

% в списку, а Чоловік – чоловік, формований вилученням зі

% списку Чоловіки. Хвіст списку Пари одержуємо рекурсивним

% викликом, передаючи процедурі вибір_пари список жінок без

% H і список чоловіків без чоловіка Чоловік

4. упорядкування тверджень у процедурі таким чином: спочатку твердження для базового стану, а потім – для рекурсивного.

5. Ці твердження взаємовиключні: вид першого аргументу в першому і другому твердженні не допускають один одного.

У результаті одержимо розв’язок

вибір_пари([ ], [ ], [ ]).

вибір_пари([H | Хвіст], Чоловіки, [пари(H,Чоловік) | Пари]):-

вилучити(Чоловік, Чоловіки, Залишок),

вибір_пари(Хвіст, Залишок, Пари).

Тут використовується недетерминированне поводження процедури “видалити”: коли до неї звертаються з неініціалізованою змінною ( Чоловік ) вона буде генерувати всі можливі значення елементів списку Чоловіки, видаляючи при цьому знайдені елементи. Тому при наступному звертанні той самий чоловік не може бути призначений у пари повторно.

Приклад 4. Додавання елемента в список.

Розробити процедуру, що виконує додавання елемента до списку:

додати(Елемент, Вихідний_список, Рез_список)

Розв’язання виходить в один рядок

додати(Елемент, Список, [Елемент | Список]).

Приклад 5. Цікавий зв'язок простежується між предикатами “належить” і “вилучити”:

належить(Елемент, Список):-

вилучити(Елемент, Список, _).

% Якщо деякий Елемент належить списку, то його можна звідти

% вилучити

Приклад 6. Поділ списків.

Розробити процедуру, що розділяє вихідний список на два: у першому містяться всі елементи, що не перевершують деякого числа, а в другому – більші за це число:

розділити(Компаратор, Вихідний_список, Менші, Більші).

Тут потрібно будувати списки, тому розв’язок буде отримано комбінуванням методу прогресуючої підстановки й аналізу станів.

Етапи:

1. Вигляділення трьох станів вхідних аргументів:

а) списки порожні;

б) списки непорожні й перший елемент вихідного списку не перевершує компаратор;

в) списки непорожні й перший елемент вихідного списку більший компаратора;

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

а) [ ];

б) [H | Хвіст] і другу умову можна розпізнати за допомогою сторожової цілі в тілі твердження H <= Компаратор);

в) [H | Хвіст] і другу умову можна розпізнати за допомогою сторожової цілі в тілі твердження H > Компаратор;

3. Реалізація поводження процедури в цих станах у такий спосіб:

а) фактом

розділити(_, [ ], [ ], [ ]).

% Якщо вихідний список порожній, те і результатом будуть

% порожні списки

б) правилом

розділити(Компаратор, [H | Хвіст],[H | Залишок], Великі):-

H <= Компаратор), % Сторожова ціль

розділити(Компаратор, Хвіст, Залишок, Великі).

% Якщо вихідний список не порожній (перевіряється

% видом аргументу в заголовку) і його перший елемент не

% перевершує поділяючий елемент (Компаратор)

% (перевіряється сторожовою ціллю тіла – її треба поставити

% першою в тілі твердження), то він переноситься в перший

% результуючий список, а залишок (Залишок) цього

% результуючого списку формується рекурсивним викликом.

% Весь другий результуючий список (Великі) у цьому випадку

% формується за допомогою рекурсивного виклику.

в) правилом

розділити(Компаратор, [H | Хвіст], Менші, [H | Залишок]):-

H > Компаратор, % Сторожова ціль

розділити(Компаратор, Хвіст, Залишок, Великі).

% Аналогічно до попереднього випадку, тільки перший

% елемент вихідного списку переноситься в другий

% результуючий список . Все інше формується рекурсивним

% викликом.

4. Упорядкування тверджень у процедурі таким чином: спочатку твердження для базового стану, а потім – для рекурсивного в довільному порядку, тому, що сторожові цілі H <= Компаратор і H > Компаратор є взаємно виключні.

5. Взаємна виключність тверджень процедури вже доведена.

У результаті одержимо розв’язок

розділити(_, [ ], [ ], [ ]).

розділити(Компаратор, [H | Хвіст],[H | Залишок], Великі):-

H <= Компаратор,

розділити(Компаратор, Хвіст, Залишок, Великі).

розділити(Компаратор, [H | Хвіст], Менші, [H | Залишок]):-

H > Компаратор,

розділити(Компаратор, Хвіст, Залишок, Великі).

Приклад 7. Сортування списків методом вставки.

Розробити процедуру, що сортує вихідний список у порядку наростання методом вставки:

сортування_вставкою(Вихідний_список, Відсортований_список)

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

уставити(Елемент, Відсортований_список, Рез_список).

Цю процедуру будемо розробляти методом аналізу станів із застосуванням прогресуючої підстановки.

Етапи:

1. Виділення двох станів вхідних аргументів:

а) елемент менше першого у вихідному списку;

б) елемент не менше першого у вихідному списку;

2. Розпізнання станів за допомогою сторожової цілі:

а) Елемент > Перший;

б) Елемент <= Перший;

3. Реалізація поводження процедури в цих станах можна у такий спосіб:

а) правилом

уставити(Елемент, [Перший | Відсортований], [Перший | Рез]):-

Елемент > Перший, !, % Сторожова ціль

уставити(Елемент, Відсортований, Рез).

% Якщо Елемент більше Першого, то про місце вставки поки нічого

% сказати не можна: Перший переносимо в результат на перше місце, а

% хвіст результату (Рез) одержуємо рекурсивним викликом (намагаємося

% його вставити в хвіст (Відсортований) вихідного списку. Відсікання

% потрібне, щоб наділити наступне твердження неявною інверсною ціллю.

а) фактом

уставити(Елемент, Вихідний_список, [Елемент | Вхідний_список]).

% У протилежному випадку вставляємо Елемент першим у результуючий

% список.

4. Упорядкування тверджень у процедурі саме таке: використання відсікання робить поводження процедури залежним від порядку розташування тверджень у процедурі.

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

У результаті одержимо розв’язок

уставити(Елемент, [Перший | Відсортований], [Перший | Рез]):-

Елемент > Перший, !, % Сторожова ціль

уставити(Елемент, Відсортований, Рез).

уставити(Елемент, Вхідний_список, [Елемент | Вхідний_список]).

Тепер можна приступити до розв’язання головної задачі. Її також будемо розв’язувати методом аналізу станів.

Етапи:

1. Виділення двох станів:

а) вихідний список – порожній;

б) вихідний список – непорожній.

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

а) [ ];

б) [Перший | Хвіст].

3) Реалізація поводження процедури в цих станах у такий спосіб:

а) фактом

сортування_вставкою([ ], [ ]).

% Якщо вихідний список порожній, то результатом також

% буде порожній список

б) правилом

сортування_вставкою([Перший |Хвіст], Відсортований_список):-

сортування_вставкою(Хвіст, Відсортований_хвіст),

уставити( Перший, Відсортований_хвіст, Відсортований_список).

% Якщо вихідний список не порожній, то розбити його на

% голову (Перший) і хвіст (Хвіст), відсортувати Хвіст за

% допомогою рекурсивного виклику і вставити голову у

% відсортований хвіст, одержуючи потрібний результат.

4. Порядок тверджень у процедурі може бути довільним.

5. Ці твердження взаємовиключні: вид першого аргументу в першому і другому твердженні виключають один одного.

У результаті одержимо остаточний розв’язок:

сортування_вставкою([ ], [ ]).

сортування_вставкою([Перший | Хвіст], Відсортований_список):-

сортування_вставкою(Хвіст, Відсортований_хвіст),

уставити( Перший, Відсортований_хвіст, Відсортований_список).

уставити(Елемент, [Перший |Відсортований], [Перший | Результуючий]):-

Елемент > Перший, % Сторожова ціль

!, % Відсікання наділяє наступне твердження процедури неявною

% інверсною сторожовою ціллю: Елемент <= Перший

уставити(Елемент, Відсортований, Результуючий).

уставити(Елемент, Вхідний_список, [Элемент | Вхідний_список]).

Якщо не застосовувати відсікання, то процедура “уставити” може мати такий вигляд:

уставити(Елемент, [Перший | Відсортований], [Перший | Рез]):-

Елемент > Перший, % Сторожова ціль

уставити(Елемент, Відсортований, Рез).

уставити(Елемент, [Перший | Відсортований],

[Елемент, Перший | Відсортований]):-

Елемент <= Перший. % Сторожова ціль