Prolog può risolvere i problemi di soddisfazione dei vincoli?


18

Il tipo di problemi di "partecipazione alle feste" è risolvibile in Prolog? Per esempio:

Bardana Muldoon e Carlotta Pinkstone dissero entrambe che sarebbero venute se fosse arrivata Albus Silente. Albus Silente e Daisy Dodderidge hanno entrambi detto che sarebbero venuti se fosse arrivata Carlotta Pinkstone. Albus Silente, Burdock Muldoon e Carlotta Pinkstone dissero che sarebbero venuti se fosse arrivata Elfrida Clagg. Carlotta Pinkstone e Daisy Dodderidge dissero entrambe che sarebbero venute se fosse arrivato Falco Aesalon. Bardana Muldoon, Elfrida Clagg e Falco Aesalon dissero tutti che sarebbero venuti se fossero venuti entrambi Carlotta Pinkstone e Daisy Dodderidge. Daisy Dodderidge disse che sarebbe venuta se fossero venuti entrambi Albus Silente e Burdock Muldoon. Chi deve essere convinto a partecipare alla festa per garantire la partecipazione di tutti i suoi invitati?

Ho cercato di esprimere questo in 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.*/

Sto riscontrando un overflow dello stack (nessun gioco di parole) e non ho conoscenza della valutazione del prologo, ecco perché lo sto chiedendo.

In generale, questo problema può essere inserito nella formula booleana di soddisfazione del CNF (con 6 variabili booleane). Pertanto, la prospettiva del prologo ha qualche merito?


2
Penso che il tuo problema sia che gli identificatori maiuscoli sono variabili, quindi attend(BM) :- attend(AD).è esattamente lo stesso diattend(X) :- attend(Y).
svick

Provato con lettere minuscole ( ideone.com/w622Z ) ha sempre lo stesso risultato.
Tegiri Nenashi,

Ovviamente non ho fatto Mercury / Prolog per troppo tempo, ovviamente il punto di svick è corretto, e il tuo primo programma corrisponde approssimativamente a dire "qualcuno è ammesso se qualcuno è ammesso". Dopo aver sostituito le variabili con termini concreti, ti imbatti nel problema spiegato nella mia risposta.
Ben

La semplice risposta è "Sì", poiché Prolog è un linguaggio completo di Turing.
David Richerby,

Risposte:


13

Per risolvere un problema con Prolog, come con qualsiasi linguaggio di programmazione, sia esso dichiarativo o imperativo, devi pensare alla rappresentazione della soluzione e dell'input.

Poiché questa è una domanda di programmazione, sarebbe stata popolare su StackOverflow.com dove i programmatori risolvono problemi di programmazione. Qui proverei a essere più scientifico.

Per risolvere il problema nel PO si deve invertire la relazione definita dalle dipendenze indicate nell'input. Le clausole del modulo sono facili da invertire. Le clausole A t t e n d ( A D ) A t t e n d (Attend(X)Attend(Y)Attend(Z) comeAttend(UND)UNttend(BM)UNttend(DD)

Daisy Dodderidge ha detto che sarebbe venuta se fossero venuti entrambi Albus Silente e Burdock Muldoon

sono più difficili da trattare.

Con Prolog il primo approccio semplice è quello di evitare una completa inversione della relazione e di essere invece diretto all'obiettivo.

Supponi un ordine nell'elenco degli ospiti e usa una regola

{UN(X)UN(Y)UN(Z),UN(W)UN(X),UN(W)UN(Y),X<Z,Y<Z}UN(W)UN(Z)

(Usiamo invece di A t t e n d ( X ) per mantenerlo breve)UN(X)UNttend(X)

Questa regola è facile da implementare.

Un approccio piuttosto ingenuo

Per leggibilità, followssia la relazione data come input, e bringssii il suo contrario.

Quindi l'input è dato da

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]).

E bringspuò essere definito come segue:

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)X

Se definiamo

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

Otteniamo le seguenti soluzioni uniche:

 [ad,ec]

Questo non è l'elenco completo, poiché sotto l'ordine alfabetico la clausola

 follows(bm,[cp,dd]).

non funziona.

Una soluzione piuttosto complicata al puzzle originale

Per risolvere completamente il problema devi effettivamente consentire al sistema di provare a provare la presenza per gli ospiti successivi senza introdurre loop infiniti nella struttura di ricerca. Esistono diversi modi per raggiungere questo obiettivo. Ognuno ha i suoi vantaggi e svantaggi.

Un modo è ridefinire brings/2come segue:

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).

L'ultimo argomento in brings/4è necessario per evitare un loop infinito in try_bring.

Questo dà le seguenti risposte: Albus, Carlotta, Elfrida e Falco. Tuttavia, questa soluzione non è la più efficiente poiché viene introdotto il backtracking in cui a volte può essere evitato.

Una soluzione generale

r(X,S):VV'

SVV'=V{X}

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).

Ora se per esempio il set di dati n. 2 viene dato come

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

Otteniamo la risposta L = [[ad, bm, dd, ec]]. Ciò significa che tutti gli ospiti tranne Carlotte e Falco devono essere invitati.

Le risposte che questa soluzione mi ha dato corrispondevano alle soluzioni fornite nell'articolo Wicked Witch con l'eccezione del set di dati n. 6, dove venivano prodotte più soluzioni. Questa sembra essere la soluzione corretta.

Infine, devo citare la libreria CLP (FD) di Prolog, particolarmente adatta a questo tipo di problemi.


La risposta corretta include anche F (ovvero A, C, E, F). Hai un refuso nelle regole o un problema più serio nel programma.
Tegiri Nenashi,


Set di dati n. 2 dal sito collegato nell'articolo ideone.com/21AmX Non sembra funzionare ...
Tegiri Nenashi,

La tua soluzione gestisce più alternative (set di dati n. 8) ideone.com/rBjXi
Tegiri Nenashi

@TegiriNenashi Ci sono 6 ipotesi "non assumere" sul sito collegato. La mia soluzione non soddisfa № 2 e № 5. № 5 sembra facile da risolvere: generalizzare due regole "% Not a follower". Se risolto, dovrebbe ottenere la prima risposta per il set di dati # 8. Fino a quando il presupposto № 2 non è soddisfatto, nessuno dei set di dati di esempio può essere risolto correttamente.
Dmitri Chubarov,

10

Come notato da svick, il primo problema con il codice nell'OP è che i nomi che iniziano con lettere maiuscole sono variabili in Prolog. Quindi admit(CP) :- admit(AD)è equivalente a attend(X) :- attend(Y), il che porta Prolog a entrare immediatamente in un ciclo infinito, cercando di dimostrare che attendvale per qualche termine trovando un termine per cui attendvale.

Tuttavia, se intendevi che ogni set di iniziali fosse un termine distinto concreto, allora ti imbatterai comunque in uno stack overflow perché hai dei cicli, ad es.

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

Quindi per capire se i attend(cp)blocchi Prolog proveranno a determinare se i attend(ad)blocchi, cosa che farà controllando se i attend(cp)blocchi, e così via fino allo straripamento dello stack.

Non credo che Vanilla Prolog faccia alcun tentativo per determinare se esiste un tale ciclo ed esaminare altri modi per rendere uno attend(cp)o attend(ad)vero piuttosto che rimanere bloccati in un ciclo infinito.

Potrebbero esistere o meno versioni di Prolog che supportano tale funzione. Ho una maggiore familiarità con Mercury e penso che il "modello minimo di presentazione" di Mercury sia esattamente ciò di cui hai bisogno per questo caso. In realtà non l'ho mai usato, ma la mia comprensione è che più o meno permette a un termine che implica se stesso di essere considerato vero se esiste un altro modo per dimostrarlo, o falso altrimenti, senza essere catturato in un ciclo infinito. Consulta la sezione pertinente dei documenti Mercury e, se sei interessato, un documento che descrive l'implementazione.

Il mercurio è un linguaggio di programmazione logico di purezza forzata, con sintassi simile a Prolog ma sistemi di tipo e modalità forti, ed è compilato piuttosto che interpretato.

Ho appena scremato l'introduzione al documento (che non ho letto da un po 'di tempo) e menziona il fatto che il tabling è stato implementato in diverse versioni di Prologs, quindi potrebbe essere che tu possa andare oltre cercando su Google il tabling in Prolog.



0

Mettendo da parte il problema delle lettere minuscole / maiuscole, c'è un ciclo nelle clausole:

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

Quindi, quando chiami un interprete top-down, verrà eseguito il ciclo. Potresti avere più fortuna con Answer Set Programming (ASP), che funziona dal basso verso l'alto. Ecco una codifica tramite libreria ( minimal / 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).

Ecco un esempio:

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).
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.