Examples in XSB Prolog Επεξεργασία και Αναπαράσταση Γνώσης Άνοιξη 2010 Τμήμα Επιστήμης Υπολογιστών Πανεπιστημίου Κρήτης
2 Typing a program on the XSB command line Ο χρήστης του XSB έχει τη δυνατότητα να εισάγει τις προτάσεις (γεγονότα και κανόνες) του προγράμματός του χρησιμοποιώντας τη γραμμή εντολών του XSB, π.χ. | ?- [user]. Enter mode to type predicates & rules. [Compiling user] edge(1,2). edge(2,3). edge(2,4). reachable(X,Y) :- edge(X,Y). reachable(X,Y) :- edge(X,Z), reachable(Z, Y). end_of_file. Done with terminal mode [user compiled, cpu time used: seconds] [user loaded] yes
3 Loading a program in XSB Έχει επίσης τη δυνατότητα να «φορτώσει» ένα αρχείο που περιέχει το λογικό πρόγραμμα. Αυτό θα πρέπει να έχει κατάληξη.P | ?- [edges]. Load and run edges.P [edges loaded] yes Για να «φορτώσει» ένα αρχείο που βρίσκεται σε διαφορετικό directory γράφετε: | ?- [‘source/edges’]. Load and run edges.P which is saved in directory ‘source’ [edges loaded] yes To default directory για τον XSB είναι το: xsb-3.1-win32/config/x86-pc-windows/bin Για να αυξήσετε το μέγεθος της κονσόλας δεξί κλικ στην γραμμή εργαλείων Προεπιλογές Διάταξη Μέγεθος παραθύρου
4 Making queries and termination | ?- reachable(X,Y). X = 1 Y = 2; Type a semi-colon repeatedly X = 2 Y = 3; X = 2 Y = 4; X = 1 Y = 3; X = 1 Y = 4; no | ?- halt. Command to Exit XSB End XSB (cputime 0.15 secs, elapsetime secs)
5 Prolog Program Semantics Τα προγράμματα στην Prolog μπορούν να γίνουν κατανοητά με δύο τρόπους: δηλωτικά (declaratively) διαδικαστικά (procedurally) Έτσι αν θεωρήσουμε την πρόταση P :- Q, R. αυτή μπορεί να ερμηνευτεί ως εξής: Δηλωτικά: το P είναι αληθές αν το Q είναι αληθές και το R είναι αληθές Διαδικαστικά: Για να αποδειχθεί ότι το P είναι αληθές, πρέπει πρώτα να αποδείξω πρώτα ότι τo Q είναι αληθές και στη συνέχεια ότι και το R είναι αληθές.
6 Loading a program in XSB Ένα αρχείο μπορεί να περιέχει: export declarations: ορίζουν τα σύμβολα που μπορούν να χρησιμοποιηθούν από άλλα αρχεία local declarations: ορίζουν τα σύμβολα με τοπική μόνο εμβέλεια import declarations: επιτρέπουν τη χρήση συμβόλων που έχουν εξαχθεί από άλλα αρχεία Οι δηλώσεις αυτές μπορούν να γίνουν σε οποιοδήποτε σημείο του προγράμματος και έχουν την ακόλουθη μορφή: :- export sym 1,...,sym l. :- local sym 1,...,sym m. :- import sym 1,...,sym n from module. όπου το sym i έχει τη μορφή functor/arity. π.χ. :- import reachable/2 from edges.
7 Useful examples – or, if-then-else Το λογικό or μπορείτε να το εκφράσετε και ως εξής: or(A):- (A = a ; A = b ), write('A is a or b'). Προσοχή να μην ξεχνάτε τις παρενθέσεις. if (X=1) then write(b) else write(d): a(X):- write(a), ( X = 1 -> write(b) ; write(d) ), write(c).
8 Useful examples - for Προσοχή να μην ξεχνάτε την συνθήκη τερματισμού. Call with i(5) Result: 4,3,2,1,0 end condition: N>0 i(0). i(N) :- N>0, write(N), nl, N1 is N-1, i(N1). Call with for(0,5) Result: 0,1,2,3,4 end condition Start<End for(End, End ). for(Start, End ):- Start<End, write(Start), nl, Start1 is Start+1, for(Start1,End).
9 Dynamic Predicates Κατηγορήματα Στατικά (static): τροποποιούνται μόνο με reloading του προγράμματος. Δυναμικά (dynamic): μπορούν να τροποποιηθούν την ώρα που τρέχει ένα πρόγραμμα. Για να μπορούμε να χειριστούμε μία δομή δυναμικά, στην αρχή του προγράμματος κάνουμε τη δήλωση: :- dynamic turn/1 ή :- dynamic pred/2. Στη συνέχεια μπορούμε να προσθέσουμε / αφαιρέσουμε δεδομένα από τη βάση γνώσης με χρήση των: assert(…): εισάγει ένα νέο γεγονός στη μνήμη. retract(…): διαγράφει ένα γεγονός από τη μνήμη.
10 XSB library modules – List processing (1/3) append(?List1, ?List2, ?List3) module: basics Succeeds if list List3 is the concatenation of lists List1 and List2. member(?Element, ?List) module: basics Checks whether Element unifies with any element of list List, succeeding more than once if there are multiple such elements. memberchk(?Element, ?List) module: basics Similar to member/2, except that memberchk/2 is deterministic, i.e. does not succeed more than once for any call. delete_ith(+Index, +List, ?Element, ?RestList) module: listutil Succeeds if the i th element of the list List unifies with Element, and RestList is List with Element removed.
11 XSB library modules – List processing (2/3) ith(?Index, ?List, ?Element) module: basics Succeeds if the i th element of the list List unifies with Element. Fails if Index is not a positive integer or greater than the length of List. Either Index and List, or List and Element, should be instantiated at the time of the call. length(?List, ?Length) module: basics Succeeds if the length of the list List is Length. same_length(?List1, ?List2) module: basics Succeeds if list List1 and List2 are both lists of the same number of elements. select(?Element, ?L1, ?L2) module: basics List2 derives from List1 by selecting (removing) an Element non- deterministically.
12 XSB library modules – List processing (3/3) reverse(+List, ?ReversedList) module: basics Succeeds if ReversedList is the reverse of list List. If List is not a proper list, reverse/2 can succeed arbitrarily many times. It works only one way. perm(+List, ?Perm) module: basics Succeeds when List and Perm are permutations of each other. The main use of perm/2 is to generate permutations of a given list. Perm may be partly instantiated. subseq(?Sequence, ?SubSequence, ?Complement) module: basics Succeeds when SubSequence and Complement are both subsequences of the list Sequence (the order of corresponding elements being preserved) and every element of Sequence which is not in SubSequence is in the Complement and vice versa. merge(+List1, +List2, ?List3) module: listutil Succeeds if List3 is the list resulting from ”merging” lists List1 and List2
13 Append example append([],L,L). append([X|L], M, [X|N]) :- append(L,M,N). append([1,2],[3,4],X)?
14 append([],L,L). append([X|L],M,[X|N]) :- append(L,M,N). append([1,2],[3,4],X)?X=1,L=[2],M=[3,4],A=[X|N] Append example
15 append([],L,L). append([X|L],M,[X|N]) :- append(L,M,N). append([1,2],[3,4],X)?X=1,L=[2],M=[3,4],A=[X|N] append([2],[3,4],N)? Append example
16 append([],L,L). append([X|L],M,[X|N’]) :- append(L,M,N’). append([1,2],[3,4],X)? X=2,L=[],M=[3,4],N=[2|N’]append([2],[3,4],N)? X=1,L=[2],M=[3,4],A=[1|N] Append example
17 append([],L,L). append([X|L],M,[X|N’]) :- append(L,M,N’). append([1,2],[3,4],X)? X=2,L=[],M=[3,4],N=[2|N’]append([2],[3,4],N)? X=1,L=[2],M=[3,4],A=[1|N] append([],[3,4],N’)? Append example
18 append([],L,L). append([X|L],M,[X|N’]) :- append(L,M,N’). append([1,2],[3,4],X)? X=2,L=[],M=[3,4],N=[2|N’]append([2],[3,4],N)? X=1,L=[2],M=[3,4],A=[1|N] append([],[3,4],N’)?L = [3,4], N’ = L Append example
19 append([],L,L). append([X|L],M,[X|N’]) :- append(L,M,N’). append([1,2],[3,4],X)? X=2,L=[],M=[3,4],N=[2|N’]append([2],[3,4],N)? X=1,L=[2],M=[3,4],A=[1|N] append([],[3,4],N’)?L = [3,4], N’ = L A = [1|N] N = [2|N’] N’= L L = [3,4] Answer: A = [1,2,3,4] Append example
20 Building new predicates - Range Create a list containing all integers within a given range. :- export range/3. range(I,I,[I]). range(I,K,[I|L]) :- I < K, I1 is I + 1, range(I1,K,L). % range(I,K,L) :- I <= K, and L is the list containing all % consecutive integers from I to K. Π.χ. | ?- range(4, 7, L). L = [4,5,6,7]; no
21 Building new predicates - Slice Extract a slice from a list. :- export slice/4. slice([X|_],1,1,[X]). slice([X|Xs],1,K,[X|Ys]) :- K > 1, K1 is K - 1, slice(Xs,1,K1,Ys). slice([_|Xs],I,K,Ys) :- I > 1, I1 is I - 1, K1 is K - 1, slice(Xs,I1,K1,Ys). % slice(L1,I,K,L2) :- L2 is the list of the elements of L1 % between index I and index K (both included). Π.χ. | ?- slice([19,21,35,43,56,68,77,89.90], 3, 5, L2). L2 = [35,43,56]; no
22 Building new predicates - Split Split a list into two parts. :- export split/4. split(L,0,[],L). split([X|Xs],N,[X|Ys],Zs) :- N > 0, N1 is N - 1, split(Xs,N1,Ys,Zs). % split(L,N,L1,L2) :- the list L1 contains the first N elements % of the list L, the list L2 contains the remaining elements. Π.χ. | ?- split([1,2,3,4,5,6,7,8,9,10], 3, L1, L2). L1 = [1,2,3] L2 = [4,5,6,7,8,9,10]; no
23 Building new predicates - Rotate Rotate a list N places to the left. :- import append/3 from basics. :- import length/2 from basics. :- import split/4 from split. :- export rotate/3. rotate(L1,N,L2) :- N >= 0, length(L1,NL1), N1 is N mod NL1, rotate_left(L1,N1,L2). rotate(L1,N,L2) :- N < 0, length(L1,NL1), N1 is NL1 + (N mod NL1), rotate_left(L1,N1,L2). rotate_left(L,0,L). rotate_left(L1,N,L2) :- N > 0, split(L1,N,S1,S2),append(S2,S1,L2). % rotate(L1,N,L2) :- the list L2 is obtained from the list L1 % by rotating the elements of L1 N places to the left. Π.χ. | ?- rotate([1,2,3,4,5,6,7,8], 2, L2). L2 = [3,4,5,6,7,8,1,2]; no
24 Building new predicates – Random Selection Extract a given number of randomly selected elements from a list. :- import random/3 from random. :- import length/2 from basics. :- import delete_ith/4 from listutil. :- export rnd_select/3. rnd_select(_,0,[]). rnd_select([X],1,[X]). rnd_select(Xs,N,[X|Zs]) :- N > 0, length(Xs,L), random(1,L,I), delete_ith(I,Xs,X,Ys), N1 is N-1, rnd_select(Ys,N1,Zs). % rnd_select(L,N,R) :- the list R contains N randomly selected % items taken from the list L. Π.χ. | ?- rnd_select([1,2,3,4,5,6,7,8,9], 5, R). R = [7,2,8,1,4]; no
25 Building new predicates – Random Permutation Generate a random permutation of the elements of a list. :- import length/2 from basics. :- import rnd_select/3 from random_selection. :- export rnd_permu/2. rnd_permu(L1,L2) :- length(L1,N), rnd_select(L1,N,L2). % rnd_permu(L1,L2) :- the list L2 is a random permutation of % the elements of the list L1. Π.χ. | ?- rnd_permu([1,2,3,4,5,6,7,8,9], L). L = [7,3,5,1,8,2,4,6,9]. no
26 Eight Queens Problem Ο στόχος είναι να τοποθετηθούν οχτώ βασίλισσες σε μία σκακιέρα χωρίς να μπορεί να απειλείται κάποια από οποιαδήποτε άλλη. Θα πρέπει επομένως η κάθε βασίλισσα να βρίσκεται σε διαφορετική γραμμή, στήλη και διαγώνιο της σκακιέρας. Η προτεινόμενη λύση αποτελεί μια γενική λύση για σκακιέρες (και αντίστοιχο πλήθος βασιλισσών) οποιουδήποτε μεγέθους. Αναπαριστούμε τις θέσεις των βασιλισσών με μία λίστα των αριθμών 1-Ν. Για παράδειγμα η λίστα [4,2,7,3,6,8,5,1] σημαίνει ότι η βασίλισσα στην πρώτη στήλη βρίσκεται στη γραμμή 4, η βασίλισσα στη δεύτερη στήλη στη γραμμή 2. κ.ο.κ. Με αυτόν τον τρόπο εξασφαλίζουμε ότι η κάθε βασίλισσα βρίσκεται σε διαφορετική στήλη και γραμμή. Το μόνο που απομένει είναι να γίνει ο έλεγχος της διαγώνιου. Μία βασίλισσα τοποθετημένη στη στήλη Χ και γραμμή Υ καταλαμβάνει δύο διαγώνιους, μία με αριθμό C = X+Y και μία με αριθμό D=X-Y.
27 Eight Queens – The Solution :- import memberchk/2 from basics. :- import range/3 from range. :- import select/3 from basics. % queens(N,Qs) :- Qs is a solution of the N-queens problem queens(N,Qs) :- range(1,N,Rs), permu(Rs,Qs), test(Qs,1,[],[]). permu([],[]). permu(Qs,[Y|Ys]) :- select(Y,Qs,Rs), permu(Rs,Ys). % test(Qs,X,Cs,Ds) :- the queens in Qs, representing columns X to % N, are not in conflict with the diagonals Cs and Ds; Cs and Ds keep % track of the already occupied diagonals. test([],_,_,_). test([Y|Ys],X,Cs,Ds) :- C is X-Y, \+ memberchk(C,Cs), D is X+Y, \+ memberchk(D,Ds), X1 is X + 1, test(Ys,X1,[C|Cs],[D|Ds]).
28 Knight’s Tour Problem Ο στόχος είναι να βρούμε μία πορεία που μπορεί να διαγράψει ένα άλογο σε όλες τις θέσεις μία σκακιέρας αυθαίρετου μεγέθους, χωρίς να επισκεφτεί την ίδια θέση παραπάνω από μία φορές.
29 Knight’s Tour – The Solution (1/2) :- import memberchk/2 from basics. % knights(N,Knights) :- Knights is a knight's tour on a NxN chessboard knights(N,Knights) :- M is N*N-1, knights(N,M,[1/1],Knights). % closed_knights(N,Knights) :- Knights is a knight's tour on a NxN % chessboard which ends at the same square where it begun. closed_knights(N,Knights) :- knights(N,Knights), Knights = [X/Y|_], jump(N,X/Y,1/1). % knights(N,M,Visited,Knights) :- the list of squares Visited must be % extended by M further squares to give the solution Knights of the % NxN chessboard knight's tour problem. knights(_,0,Knights,Knights). knights(N,M,Visited,Knights) :- Visited = [X/Y|_], jump(N,X/Y,U/V), \+ memberchk(U/V,Visited), M1 is M-1, knights(N,M1,[U/V|Visited],Knights).
30 Knight’s Tour – The Solution (2/2) % jumps on an NxN chessboard from square A/B to C/D jump(N,A/B,C/D) :- jump_dist(X,Y), C is A+X, C > 0, C =< N, D is B+Y, D > 0, D =< N. % jump distances jump_dist(1,2). jump_dist(2,1). jump_dist(2,-1). jump_dist(1,-2). jump_dist(-1,-2). jump_dist(-2,-1). jump_dist(-2,1). jump_dist(-1,2).
31 Το πρόβλημα του βοσκού Το πρόβλημα του βοσκού αποτελεί ένα κλασσικό πρόβλημα λογικής (puzzle). Σε μια όχθη ενός ποταμού υπάρχουν ένας βοσκός, ένας λύκος ένα πρόβατο και ένα δεμάτι σανός. Η διαθέσιμη βάρκα χωρά μόνο δύο αντικείμενα κάθε φορά. Αν υποθέσουμε ότι σε κάθε στιγμή ο λύκος και το πρόβατο, καθώς και το πρόβατο και το δεμάτι δεν μπορούν να είναι μόνα τους σε μία όχθη (χωρίς τον βοσκό), ποία είναι η ακολουθία κινήσεων η οποία πρέπει να γίνει για να περάσουν όλοι απένταντι; Το παραπάνω πρόβλημα είναι ουσιαστικά ένα πρόβλημα σχεδιασμού κινήσεων (Planning). Στα προβλήματα της κατηγορίας αυτής, μία λύση είναι η αναζήτηση στο χώρο των καταστάσεων του κόσμου του προβλήματος. Κάθε τέτοια κατάσταση περιγράφει τον "κόσμο" του προβλήματος την συγκεκριμένη χρονική στιγμή. Οι μεταβάσεις από μια συγκεκριμένη κατάσταση σε μια επόμενη γίνονται μέσω τελεστών.
32 Επιλογή Αναπαράστασης Είναι πολλές οι διαθέσιμες επιλογές με τις οποίες μπορούμε να αναπαραστήσουμε το συγκεκριμένο πρόβλημα. Η παρακάτω υλοποίηση χρησιμοποιεί για την περιγραφή της κατάστασης ένα σύνθετo όρο της μορφής: state(shepherd(A),sheep(B),wolf(C),hey(D)) όπου τα A, B, C, D είναι οι όχθες στις οποίες βρίκονται αντίστοιχα ο βοσκός, το πρόβατο, ο λύκος και το δεμάτι σανός. Με βάση την παραπάνω αναπαράσταση ορίζονται οι τελεστές, που αντιστοιχούν ουσιαστικά στις κινήσεις που μπορούν να γίνουν για να λυθεί το πρόβλημα.
33 Ορισμός Κατηγορημάτων Το κατηγόρημα move/3 περιγράφει τις επιτρεπτές κινήσεις που μπορούν να γίνουν. Η μεταβλητή State είναι η "τρέχουσα" κατάσταση, η μεταβλητή Move είναι η κίνηση που λαμβάνει χώρα για να προκύψει η νέα κατάσταση που ενοποιείται με την μεταβλητή NewState. % move/3 % move(State,Move,NewState) % Move the shepherd move(state(shepherd(X1),sheep(S),wolf(W),hey(H)), go_to_other_shore(X1,X2), state(shepherd(X2),sheep(S),wolf(W),hey(H))):- opposite(X1,X2). % Take the sheep to the other side move(state(shepherd(X1),sheep(X1),wolf(W),hey(H)), move_sheep(X1,X2), state(shepherd(X2),sheep(X2),wolf(W),hey(H))):- opposite(X1,X2).
34 Ορισμός Κατηγορημάτων % Take the wolf to the other side move(state(shepherd(X1),sheep(S),wolf(X1),hey(H)), move_wolf(X1,X2), state(shepherd(X2),sheep(S),wolf(X2),hey(H))):- opposite(X1,X2). % Take the hey to the other side move(state(shepherd(X1),sheep(S),wolf(W),hey(X1)), move_hey(X1,X2), state(shepherd(X2),sheep(S),wolf(W),hey(X2))):- opposite(X1,X2). Το κατηγόρημα opposite/2 δηλώνει τις δύο απέναντι όχθες. % opposite/2 % opposite(X1,X2). opposite(a,b). opposite(b,a).
35 Ορισμός Κατηγορημάτων Το κατηγόρημα is_valid/1 πετυχαίνει όταν η κατάσταση State είναι επιτρεπτή, δηλαδή δεν παραβιάζει τους περιορισμούς του προβλήματος.. % is_valid/1 % is_valid(State). is_valid(state(shepherd(X),sheep(X),wolf(_),hey(_))). is_valid(state(shepherd(X),sheep(_),wolf(X),hey(X))).
36 Ορισμός Κατηγορημάτων Το κατηγόρημα end_state/1 πετυχαίνει όταν η State είναι η τελική κατάσταση, δηλαδή όλα τα αντικείμενα είναι στην όχθη b (θεωρούμε ότι αρχικά όλα τα αντικείμενα είναι στην όχθη a). % end_state/1 % end_state(State). end_state(state(shepherd(b),sheep(b),wolf(b),hey(b))).
37 Ορισμός Κατηγορημάτων Το κατηγόρημα solve/3 βρίσκει την σειρά των κινήσεων που πρέπει να γίνουν για να επιλυθεί το πρόβλημα. Ουσιαστικά ο αλγόριθμος αναζήτησης που χρησιμοποιείται είναι η κατα-βάθος αναζήτηση της Prolog. Η μεταβλητή State είναι η "τρέχουσα" κατάσταση, η μεταβλητή Moves η λίστα των κινήσεων και η Previous_States η λίστα των καταστάσεων τις οποίες έχει "επισκευτεί" μέχρι τώρα η διαδικασία αναζήτησης. Κάθε νέα κατάσταση που προκύπτει ( NextState ), δεν πρέπει να ανήκει στην λίστα αυτή για την αποφυγή βρόχων. % solve/3 % solve(State,Moves,Previous_States) solve(State,[],_):- end_state(State). solve(State,[Move|Moves],Previous_States):- move(State,Move,NextState), \+ member(NextState,Previous_States), is_valid(NextState), solve(NextState,Moves,[NextState|Previous_States]).
38 Ορισμός Κατηγορημάτων Το επόμενο κατηγόρημα τυπώνει στην οθόνη τα μέλη μιας λίστας, και χρησιμοποιείται για να τυπώνει τις κινήσεις στην οθόνη. % pretty_write/1 % pretty_write(List) pretty_write([]). pretty_write([First|Rest]):- write(' -> '), write(First), nl, pretty_write(Rest). Το κατηγόρημα run/1, χρησιμοποιείται για να εκτελέσουμε το πρόγραμμα. run(Moves):-solve(state(shepherd(a),sheep(a),wolf(a),hey(a)),Moves, [state(shepherd(a),sheep(a),wolf(a),hey(a))]), pretty_write(Moves),nl.
39 Ασκήσεις (ασκ.1): Δημιουργήστε διαδικασία factorial που θα υπολογίζει το παραγοντικό ενός αριθμού. Π.χ. ?- factorial(4,X). X=24 (ασκ.2): Δημιουργήστε διαδικασία median που θα υπολογίζει τον μεσαίο μίας λίστας που περιέχει αριθμούς περιττού πλήθους. Π.χ. ?- median([2,8,6,4,7,9,3],X). X=6 (ασκ.3): Δημιουργήστε διαδικασία set_equality που θα ελέγχει αν δύο λίστες περιέχουν τα ίδια στοιχεία (δεν μας ενδιαφέρει η σειρά που έχουν στη λίστα). Π.χ. ?- set_equality([1,2,3],[3,1,2]). yes ?- set_equality([1,2,3],[3,1]). no
40 Ασκήσεις (ασκ.4): Λύστε το πρόβλημα των ιεραπόστολων με τους κανίβαλους. Περιγραφή προβλήματος: Στις όχθες ενός ποταµού υπάρχουν συνολικά τρεις ιεραπόστολοι και τρεις κανίβαλοι έτσι ώστε σε κάθε όχθη να υπάρχει ίσος αριθµός ιεραποστόλων και κανιβάλων. Επίσης, υπάρχει και µια βάρκα σε µια από τις όχθες. Θέλουµε όλοι να µεταφερθούν σε µια όχθη. Όµως, η βάρκα για να κινηθεί χρειάζεται τουλάχιστον ένα άτοµο, χωρά µόνο µέχρι δύο άτοµα και σε καµία περίπτωση δεν πρέπει ο αριθµός των κανιβάλων να είναι µεγαλύτερος από αυτόν των ιεραποστόλων σε κάποια όχθη. nice link: