Чи можна вирішити проблеми із задоволенням обмеженнями за допомогою Prolog?


18

Чи вирішені проблеми "відвідування партії" в Prolog? Наприклад:

Бердок Малдун та Карлотта Пінкстоун сказали, що приїдуть, якщо приїде Альбус Дамблдор. Альбус Дамблдор і Дейзі Доддрідж заявили, що приїдуть, якщо приїде Карлотта Пінкстоун. Альбус Дамблдор, Бердок Малдун і Карлотта Пінкстоун всі сказали, що приїдуть, якщо приїде Ельфріда Клагг. Карлотта Пінкстоун та Дейзі Доддрідж заявили, що приїдуть, якщо приїде Фалько Есалон. Лопух Малдун, Ельфріда Клагг та Фалько Есалон сказали, що приїдуть, якщо приїдуть обидва Карлотта Пінкстоун та Дейзі Доддрідж. Дейзі Доддрідж сказала, що приїде, якщо приїдуть Альбус Дамблдор та Бердок Малдун. Кого потрібно переконати відвідувати вечірку, щоб забезпечити присутність усіх її запрошених?

Я спробував це висловити в GNU Prolog:

attend(BM) :- attend(AD).
attend(CP) :- attend(AD).
attend(AD) :- attend(CP).
attend(DD) :- attend(CP). 
attend(AD) :- attend(EC).
attend(BM) :- attend(EC).
attend(CP) :- attend(EC). 
attend(CP) :- attend(FA).
attend(DD) :- attend(FA).
attend(BM) :- attend(CP),attend(DD).
attend(EC) :- attend(CP),attend(DD).
attend(FA) :- attend(CP),attend(DD).
attend(DD) :- attend(AD),attend(BM).

attend(FA). /* try different seed invitees in order to see if all would attend*/

/* input:
write('invited:'),nl,
  attend(X),write(X),nl,
  fail.*/

Я відчуваю переповнення стека (не каламбур) і не знаю про оцінку прологів, ось чому я запитую.

Взагалі кажучи, ця проблема може бути включена до булевої формули задоволення CNF (з 6 булевими змінними). Отже, чи має перспектива пролог?


2
Я думаю, що ваша проблема полягає в тому, що великі ідентифікатори є змінними, тому attend(BM) :- attend(AD).точно так само, якattend(X) :- attend(Y).
svick

Спробувавши маленькими літерами ( ideone.com/w622Z ) все той же результат.
Тегірі Ненасі

Я, очевидно, не робив жодного Меркурія / Прологу занадто довго, звичайно, точка Свикка є правильною, і ваша перша програма приблизно відповідає тому, що "хтось допускається, якщо хтось допущений". Замінивши змінні на конкретні терміни, ви стикаєтеся з проблемою, поясненою в моїй відповіді.
Бен

Проста відповідь - "Так", оскільки Prolog - це повна мова Тьюрінга.
Девід Річербі

Відповіді:


13

Щоб вирішити проблему з Prolog, як і з будь-якою мовою програмування, будь то декларативною чи імперативною, потрібно подумати про подання рішення та вхідні дані.

Оскільки це питання програмування, воно було б популярним на StackOverflow.com, де програмісти вирішують проблеми програмування. Тут я б намагався бути більш науковим.

Щоб вирішити задачу в ОП, потрібно змінити відношення, визначене залежностями, зазначеними у вхідних даних. Статті форми легко відміняти. Статті A t t e n d ( A D ) A t t e n d (Аттенг(Х)Аттенг(Y)Аттенг(Z) якАттенг(АD)Аттенг(БМ)Аттенг(DD)

Дейзі Доддрідж сказала, що приїде, якщо приїдуть Альбус Дамблдор і Бердок Малдун

важче піддаються лікуванню.

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

Припустимо замовлення в списку гостей і скористайтеся правилом

{А(Х)А(Y)А(Z),А(W)А(Х),А(W)А(Y),Х<Z,Y<Z}А(W)А(Z)

(Ми використовуємо замість A t t e n d ( X ), щоб тримати його коротко)А(Х)Аттенг(Х)

Це правило легко здійснити.

Досить наївний підхід

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

Тоді вхід подається через

follows(bm,[ad]).
follows(cp,[ad]).
follows(ad,[cp]).
follows(dd,[cp]).
follows(ad,[ec]).
follows(bm,[ec]).
follows(cp,[ec]).
follows(cp,[fa]).
follows(dd,[fa]).
follows(bm,[cp,dd]).
follows(ec,[cp,dd]).
follows(fa,[cp,dd]).
follows(dd,[ad,bm]).

І bringsможна визначити так:

brings(X,S):-brings(X,S,[]).

brings(_X,[],_S).
brings(X,[X|L],S):-brings(X,L,[X|S]).
brings(X,[Y|L],S):-follows(Y,[X]),brings(X,L,[Y|S]).
brings(X,[Y|L],S):-follows(Y,[A,B]),
          member(A,S),member(B,S),brings(X,L,[Y|S]).

Тут третій аргумент brings/3(X,L,S)- це список гостей, до яких вже було доведено відвідування, якщо Х Відвідує.

Якщо ми визначимось

 partymaker(X):-Guests=[ad,bm,cp,dd,ec,fa],member(X,Guests),brings(X,Guests).

Ми отримуємо такі унікальні рішення:

 [ad,ec]

Це не повний перелік, оскільки в алфавітному порядку вказується пункт

 follows(bm,[cp,dd]).

не працює.

Досить залучене рішення оригінальної головоломки

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

Один із способів - переосмислити brings/2наступним чином:

brings(X,S):-brings(X,S,[],[]).

% brings(X,RemainsToBring,AlreadyTaken,AlreadyTried).
%
% Problem solved
brings(_X,[],_S,_N). 
% Self
brings(X,[X|L],S,N):-brings(X,L,[X|S],N). 
% Follower
brings(X,[Y|L],S,N):-follows(Y,[X]),brings(X,L,[Y|S],N). 
% Y is not a follower, but X can bring 2
brings(X,[Y|L],S,N):- \+member(Y,N),\+follows(Y,[X]), 
                   follows(Y,[A,B]),
                   try_bring(X,A,L,S,[Y|N]),
                   try_bring(X,B,L,S,[Y|N]),brings(X,L,[Y|S],N).
% Y is not a follower, but X can bring 1
brings(X,[Y|L],S,N):- \+member(Y,N),\+follows(Y,[X]),\+follows(Y,[_A,_B]), 
                   follows(Y,[C]),
                   try_bring(X,C,L,S,[Y|N]),brings(X,L,[Y|S],N).

try_bring(_X,A,_L,S,_N):-member(A,S).
try_bring(X,A,L,S,N):- \+member(A,S),sort([A|L],Y),brings(X,Y,S,N).

Останній аргумент в brings/4 необхідний, щоб уникнути нескінченного циклу try_bring.

Це дає наступні відповіді: Альбус, Карлотта, Ельфріда та Фалько. Однак це рішення не є найбільш ефективним, оскільки зворотний трек вводиться там, де його іноді можна уникнути.

Загальне рішення

r(Х,S):VV'

SVV'=V{Х}

VUV

add_element(X,V,U):- ( var(V) -> % set difference that works in both modes
                           member(X,U),subtract(U,[X],V);
                      \+member(X,V),sort([X|V],U) ).

support(V,U):- guests(G), % rule application
               member(X,G),
               add_element(X,V,U),
               follows(X,S),
               subset(S,V).

set_support(U,V):- support(V1,U), % sort of a minimal set
               ( support(_V2,V1) -> 
                      set_support(V1,V) ; 
                 V = V1). 

is_duplicate(X,[Y|L]):- ( subset(Y,X) ; is_duplicate(X,L) ).

% purging solutions that are not truly minimal
minimal_support(U,L):-minimal_support(U,[],L).
minimal_support([],L,L).
minimal_support([X|L],L1,L2):-( append(L,L1,U),is_duplicate(X,U) -> 
                                    minimal_support(L,L1,L2); 
                                minimal_support(L,[X|L1],L2) ).


solution(L):- guests(G),setof(X,set_support(G,X),S),
              minimal_support(S,L).

Тепер якщо, наприклад, набір даних №2 заданий як

follows(fa,[dd,ec]).
follows(cp,[ad,bm]).
guests([ad,bm,cp,dd,ec,fa]).

Отримуємо відповідь L = [[ad, bm, dd, ec]]. Що означає, що треба запросити всіх гостей, окрім Карлотти та Фалько.

Відповіді на це рішення дали мені відповідність із рішеннями, наведеними у статті Wicked Witch, за винятком набору даних № 6, де було вироблено більше рішень. Це здається правильним рішенням.

Нарешті, я повинен згадати бібліотеку CLP (FD) Prolog, яка особливо підходить для подібних проблем.


Правильна відповідь також включає F (тобто A, C, E, F). У вас або помилка в правилах, або більш серйозна проблема в програмі.
Тегірі Ненаші

Підтверджено: ideone.com/Q3pqU
Тегірі Ненаші

Набір даних №2 із сайту, пов’язаний у статті ideone.com/21AmX Це, здається, не працює ...
Тегірі Ненаші,


@TegiriNenashi На сайті пов’язано 6 припущень "не припускати". Моє рішення не відповідає № 2 і № 5. № 5 видається легко виправити: узагальнити два правила "% Не послідовник". Якщо це вирішено, він повинен отримати першу відповідь для набору даних №8. Поки припущення № 2 не виконане, жоден із прикладів наборів даних не може бути розв'язаний правильно.
Дмитро Чубаров

10

Як помітив svick, перше питання з кодом в ОП - це те, що імена, що починаються з великих літер, є змінними в Prolog. Таким чином, admit(CP) :- admit(AD)це рівнозначно attend(X) :- attend(Y), що призводить до того , що Пролог негайно вводить нескінченний цикл, намагаючись продемонструвати, що attendмає місце протягом певного терміну, знайшовши якийсь термін, для якогоattend виконується.

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

attend(cp) :- attend(ad).
attend(ad) :- attend(cp).

Отже, щоб розробити, чи attend(cp)буде Hold Prolog, спробує визначити, чи attend(ad)має місце тримання, що це буде робити, перевіряючи, чи attend(cp)має місце утримування тощо, поки стек не переповнюється.

Я не вірю, що ванільний Пролог робить будь-яку спробу визначити, чи існує такий цикл, і вивчити інші способи зробити один із справжніх attend(cp)чи attend(ad)справжнім, а не застрягти у нескінченному циклі.

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

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

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



0

Відклавши питання про малі / великі регістри, в пунктах є цикл:

attend(cp) :- attend(ad).
attend(ad) :- attend(cp).

Отже, коли ви зателефонуєте перекладача зверху вниз, він замикається. Можливо, вам пощастить із програмуванням набору відповідей (ASP), яке працює знизу вгору. Ось кодування за допомогою бібліотеки ( мінімальне / asp ):

:- use_module(library(minimal/asp)).

choose([admit(bm)]) <= posted(admit(ad)).
choose([admit(cp)]) <= posted(admit(ad)).
choose([admit(ad)]) <= posted(admit(cp)).
choose([admit(dd)]) <= posted(admit(cp)).
choose([admit(ad)]) <= posted(admit(ec)).
choose([admit(bm)]) <= posted(admit(ec)).
choose([admit(cp)]) <= posted(admit(ec)).
choose([admit(cp)]) <= posted(admit(fa)).
choose([admit(dd)]) <= posted(admit(fa)).
choose([admit(bm)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(ec)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(fa)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(dd)]) <= posted(admit(ad)),posted(admit(bm)).

choose([admit(fa)]) <= posted(init).

Ось приклад запуску:

Jekejeke Prolog 3, Runtime Library 1.3.8 (23 May 2019)

?- post(init), listing(admit/1).
admit(fa).
admit(cp).
admit(ad).
admit(bm).
admit(dd).
admit(ec).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.