Λογικός Προγραµµατισµός - Η γλώσσα προγραµµατισµού Prolog

moldwarpsurprisedΤεχνίτη Νοημοσύνη και Ρομποτική

18 Ιουλ 2012 (πριν από 5 χρόνια και 1 μήνα)

390 εμφανίσεις

1
Λογικός
Προγραµµατισµός
 Η γλώσσα προγραµµατισµού Prolog
Αλγόριθµοι αναζήτησης σε Prolog
Γιάννης Ρεφανίδης
2
Προτάσεις Horn
(1/2)

Οι προτάσεις Horn ή Οριστικές Προτάσεις (Definite Clauses)
αποτελούν ένα περιορισµένο υποσύνολο της λογικής πρώτης τάξης.

Ειδικότερα, µια πρόταση Horn µπορεί να έχει τη µορφή:

A1 ∧ A2 ∧... ∧ AN → B

ή όπως συνηθέστερα γράφεται:

B ←A1 ∧ A2 ∧... ∧ AN

ή ακόµη:

B ∨ ¬A1 ∨ ¬A2 ∨... ∨ ¬AN

Η πρόταση

B ←

δηλώνει ότι η πρόταση Β είναι αληθής.

∆εν µπορούν όλες οι βάσεις γνώσεις να µεταφραστούν σε
προτάσεις Horn, λόγω της µη δυνατότητας ύπαρξης αρνητικών
συµβόλων
2
Γιάννης Ρεφανίδης
3
Προτάσεις Horn
(2/2)

Η µορφή που έχουν οι προτάσεις Horn έχει ως αποτέλεσµα να µην
είναι δυνατόν να αποδεικνύονται αρνητικές προτάσεις.

Για το λόγο αυτό, οι ερωτήσεις που κάνουµε σε ένα σύστηµα
προτάσεων Horn αφορούν την απόδειξη θετικών µόνο προτάσεων.

Έτσι ένα σύστηµα προτάσεων Horn δεν µπορεί ποτέ να πέσει σε
αντίφαση, µε την έννοια ότι δεν µπορεί ποτέ να αποδείξει τις
προτάσεις P και ¬P ταυτόχρονα.

«Καταχρηστικά» πολλές φορές θεωρούµε ότι η µη δυνατότητα
απόδειξης της πρότασης P ισοδυναµεί µε την απόδειξη της
πρότασης ¬P.
Γιάννης Ρεφανίδης
4
Λογικός προγραµµατισµός
(1/2)

Ο λογικός προγραµµατισµός αποτελεί την πιο διαδεδοµένη
υλοποίηση της λογικής πρώτης τάξης µέσω της γλώσσας λογικού
προγραµµατισµού Prolog.

Ο λογικός προγραµµατισµός βασίζεται στις προτάσεις Horn.

Η γλώσσα Prolog (όπως και η γλώσσα συναρτησιακού
προγραµµατισµού LISP) βοηθά στη γρήγορη ανάπτυξη εφαρµογών
τεχνητής νοηµοσύνης.

Παρέχει:

Ευκολία στο χειρισµό συµβόλων

Ενσωµατωµένο µηχανισµό ταυτοποίησης/ενοποίησης

Ενσωµατωµένο αλγόριθµο αναζήτησης πρώτα-κατά-βάθος

Χρήσιµο link: http://www.afm.sbu.ac.uk/logic-prog/
3
Γιάννης Ρεφανίδης
5
Λογικός προγραµµατισµός
(2/2)

Οι προτάσεις στο λογικό προγραµµατισµό µπορούν να περιέχουν
µεταβλητές, οι οποίες όµως θεωρούνται όλες
καθολικά
ποσοτικοποιηµένες.

Πατέρας(x,y) ←Γονιός(x,y) ∧ Άρεν(x)

Το σύµβολο του καθολικού ποσοδείκτη το παραλείπουµε.

Υπάρχουν ενσωµατωµένες συναρτήσεις για αριθµητικές πράξεις.

Υπάρχουν κατηγορήµατα διαδικαστικής υφής όπως π.χ.
κατηγορήµατα εισόδου/εξόδου.

Η στρατηγική αναζήτησης είναι πρώτα-κατά-βάθος.

Έχει σηµασία η σειρά µε την οποία είναι γραµµένες οι προτάσεις
στη βάση γνώσης.

Η αναζήτηση µπορεί να παγιδευθεί σε κλαδιά άπειρου βάθους.
Γιάννης Ρεφανίδης
6
Σύνταξη
(1/2)

Τα ονόµατα των κατηγορηµάτων, των αντικειµένων και των
συναρτησιακών όρων ξεκινούν µε πεζά γράµµατα.

Τα ονόµατα των µεταβλητών ξεκινούν µε κεφαλαίο γράµµα.

Κάθε πρόταση τερµατίζει µε τελεία.

Γεγονότα (απλές προτάσεις) :

άνθρωπος (σωκράτης).

Κανόνες (σύνθετες προτάσεις):

θνητός(Χ) :- άνθρωπος(Χ).

Για κάθε Χ, εάν το Χ είναι άνθρωπος τότε το Χ είναι θνητός.

πατέρας(Χ,Υ) :- γονιός(Χ,Υ), άρεν(Χ).

Για κάθε Χ και Υ, το Χ είναι πατέρας του Υ εάν το Χ είναι γονιός του Υ και
επιπλέον το φύλο του Χ είναι αρσενικό.
4
Γιάννης Ρεφανίδης
7
Σύνταξη
(2/2)

Συγκρίνοντας τις προτάσεις:

πατέρας(Χ,Υ) :- γονιός(Χ,Υ), άρεν(Χ).

Πατέρας(x,y) ←Γονιός(x,y) ∧ Άρεν(x)

βλέπουµε ότι χρησιµοποιείται το:- αντί για ←και το κόµµα αντί για
το ∧.

Προτάσεις σαν την παραπάνω ονομάζονται κανόνες και
ερμηνεύονται ως εξής:

Για να ισχύει το πατέρας(X,Y) πρέπει να ισχύουν ταυτόχρονα τα
γονιός(Χ,Υ) και άρεν(Χ).

ή ισοδύναμα

Για κάθε Χ,Υ, εάν ισχύουν τα γονιός(Χ,Υ) και άρεν(Χ) τότε ισχύει και το
πατέρας(Χ,Υ).

Το συμπέρασμα ( πατέρας(Χ,Υ) ) ονομάζεται κεφαλή (head) του
κανόνα ενώ οι προϋποθέσεις (γονιός(Χ,Υ), άρεν(Χ) ) ονοµάζονται
σώµα του κανόνα.
Γιάννης Ρεφανίδης
8
∆ιαδικασίες

Ένα σύνολο από προτάσεις, γεγονότα ή κανόνες, όπου στην
κεφαλή τους έχουν όλες το ίδιο κατηγόρηµα και µε τον ίδιο αριθµό
ορισµάτων, ονοµάζεται διαδικασία.

Παρακάτω φαίνεται µια διαδικασία που αφορά το κατηγόρηµα
uncle/2 και ορίζει πότε ένας άνθρωπος είναι θείος ενός άλλου.

uncle(X,Y) :-
brother(X,Z),
father(Z,Y).

uncle(X,Y):-
brother(X,Z),
mother(Z,Y).
5
Γιάννης Ρεφανίδης
9
Υποβολή ερωτήσεων
(1/6)

Η Prolog είναι συνήθως µια διερµηνευόµενη γλώσσα. Ο χρήστης
φορτώνει το πρόγραµµα στη µνήµη και στη συνέχεια υποβάλλει
ερωτήσεις για τις οποίες περιµένει απάντηση.

Ο διερµηνέας της Prolog εµφανίζει την προτροπή:

?-

για την υποβολή ερωτήσεων.

Οι ερωτήσεις µπορούν να αποτελούνται από σύζευξη ενός ή
περισσοτέρων κατηγορηµάτων µε ή χωρίς µεταβλητές.

Κάθε ερώτηση πρέπει να τερµατίζει µε τελεία.
Γιάννης Ρεφανίδης
10
Υποβολή ερωτήσεων
(2/6)

Για παράδειγµα, έστω η παρακάτω βάση γνώσης (το"πρόγραµµα") σε
Prolog:

parent(nick,maria).

parent(nick, george).

male(nick).

male(george).

female(maria).

father(X,Y):-parent(X,Y), male(X).

mother (X,Y):-parent(X,Y), female(X).

Υποβάλλουµε την ερώτηση:

?-father(nick,maria).

...και παίρνουµε την απάντηση:

yes.

Προσοχή
: Οι
µεταβλητές
διαφορετικών
προτάσεων
θεωρούνται
πάντα
διαφορετικές,
ακόµη και αν
έχουν το ίδιο
όνοµα.
6
Γιάννης Ρεφανίδης
11
Υποβολή ερωτήσεων
(3/6)

Παρόµοια, υποβάλλουµε την ερώτηση:

?-father(maria,nick).

...και παίρνουµε την απάντηση:

no.

Μια ερώτηση µπορεί να περιέχει µεταβλητές, όπως π.χ.:

?- father(nick,Z).

Το νόηµα µιας τέτοιας ερώτησης είναι να ελεγχθεί εάν υπάρχουν
τιµές για τη µεταβλητή Ζ, για τις οποίες να ισχύει το father(nick,Z).

Βλέπουµε δηλαδή ότι οι µεταβλητές στις ερωτήσεις θεωρούνται
υπαρξιακά ποσοτικοποιηµένες.

ΠΡΟΣΟΧΗ
: Οι µεταβλητές στις ερωτήσεις είναι διαφορετικές από τις
µεταβλητές των προτάσεων της βάσης γνώσης, ακόµη και αν έχουν
ίδια ονόµατα.
Γιάννης Ρεφανίδης
12
Υποβολή ερωτήσεων
(4/6)

Στην περίπτωση ερώτησης µε µεταβλητές και εφόσον υπάρχουν
τιµές για τις µεταβλητές για τις οποίες να ισχύει η ερώτηση, η Prolog
απαντά µε την ανάθεση των τιµών στις µεταβλητές (αντί για ένα
απλό yes):

?- father(nick,Z).

Z=maria

Αφού εµφανίσει µια πρώτη δυνατή ανάθεση τιµών στις µεταβλητές,
µπορούµε να ζητήσουµε να ψάξει για άλλες τέτοιες"λύσεις". Για το
σκοπό αυτό πατάµε το πλήκτρο;ή το κενό (spacebar), οπότε η
Prolog απαντά:

Ζ=george

Στην παραθυρική έκδοση της ECLiPSe ζητούµε επιπλέον λύσεις
πατώντας το κουµπί More.
7
Γιάννης Ρεφανίδης
13
Υποβολή ερωτήσεων
(5/6)

Εάν για µια ερώτηση µε µεταβλητές η Prolog δεν µπορέσει να βρει
καµία δυνατή ανάθεση τιµών σε αυτές, τότε απαντά αρνητικά:

?- father(X,nick).

no

Μπορούµε να έχουµε περισσότερες από µια µεταβλητές σε µια
ερώτηση:

?- father(A,B).

οπότε η απάντηση, εφόσον υπάρχει, µας δίνει τιµές για όλες τις
µεταβλητές:

Α=nick, B=maria

και αν ζητήσουµε και δεύτερη λύση παίρνουµε την:

Α=nick, B=george
Γιάννης Ρεφανίδης
14
Υποβολή ερωτήσεων
(6/6)

Τέλος, µια ερώτηση µπορεί να είναι σύζευξη 2 ή περισσότερων
απλών προτάσεων (µε ή και χωρίς µεταβλητές):

?- male(Z), father (nick,Z).

Η ερώτηση αυτή ερµηνεύεται ως:

Βρες τα αντικείµενα εκείνα Ζ για τα οποία ισχύουν ταυτόχρονα οι σχέσεις
father (nick,Z) και male(Z).

ή σε απλά ελληνικά:

Βρες τα παιδιά του nick τα οποία είναι αγόρια.

Η Prolog θα απαντήσει:

Z=george
8
Γιάννης Ρεφανίδης
15
Εξαγωγή συµπερασµάτων
(1/3)

Η διαδικασία απόδειξης ξεκινά µε την υποβολή µιας ερώτησης, έστω
π.χ.:

male(Z), father(nick,Z).

Η Prolog προσπαθεί να"αποδείξει" µία-µία τις επιµέρους προτάσεις
της ερώτησης.

Για να αποδειχθεί µια πρόταση, πρέπει αυτή να ταυτοποιηθεί είτε:

µε ένα γεγονός της βάσης γνώσης, είτε

µε την κεφαλή ενός κανόνα της βάσης γνώσης.

Στην περίπτωση ταυτοποίησης µε την κεφαλή ενός κανόνα, το
πρόβληµα"µετατίθεται" στην απόδειξη όλων των επιµέρους
προτάσεων στο σώµα του κανόνα.
Γιάννης Ρεφανίδης
16
Εξαγωγή συµπερασµάτων
(2/3)

Για παράδειγµα, η πρόταση male(Z) ταυτοποιείται µε την
πρόταση:

male(nick).

µε την αντικατάσταση {Z/nick}.

Στη συνέχεια η πρόταση father(nick,Z), η οποία πλέον έχει γίνει
father(nick,nick), ταυτοποιείται µε την κεφαλή του κανόνα:

father(X,Y):-parent(X,Y), male(X).

µε την αντικατάσταση {X/nick, Y/nick}, οπότε πλέον το πρόβληµα
µετατίθεται στην απόδειξη των προτάσεων:

parent(nick,nick), male(nick)
9
Γιάννης Ρεφανίδης
17
Εξαγωγή συµπερασµάτων
(3/3)

Η προσπάθεια απόδειξης της προτάσης:

parent(nick,nick)

θα αποτύχει, οπότε ο µηχανισµός αναζήτησης της Prolog θα
αναζητήσει εναλλακτικούς τρόπους απόδειξης.

Θα βρει ότι η αρχική πρόταση:

male(Z)

µπορούσε να ταυτοποιηθεί και µε το γεγονός:

male(george)

της βάσης γνώσης, κάτι που οδηγούσε στην ανάγκη για απόδειξη
των γεγονότων:

parent(nick,george), male(george)

τα οποία υπάρχουν στη βάση γνώσης. Η απάντηση που τελικά
παίρνουµε είναι η:

Z=george
Γιάννης Ρεφανίδης
18
Παρατηρήσεις

Κατά την προσπάθεια απάντησης της ερώτησης του χρήστη, η Prolog
προσπαθεί να ταυτοποιήσει την ερώτηση µε κάποιο γεγονός ή την
κεφαλή κάποιου κανόνα.

Κατά την ταυτοποίηση οι µεταβλητές που υπάρχουν στην ερώτηση
του χρήστη παίρνουν τιµές (δεσµεύονται).

Μια µεταβλητή που έχει πάρει κάποια τιµή δεν µπορεί να την αλλάξει
στο τρέχον µονοπάτι (κλαδί) αναζήτησης.

Ο µόνος τρόπος µια µεταβλητή να αλλάξει τιµή είναι το µονοπάτι
αναζήτησης στο οποίο έγινε η δέσµευση να αποτύχει.

Σε αυτή την περίπτωση όλες οι µεταβλητές που είχαν πάρει τιµές σε
αυτό το µονοπάτι αναζήτησης, τις χάνουν.

Η Prolog θα δοκιµάσει εναλλακτικά µονοπάτια αναζήτησης, στα οποία
θα γίνουν νέες αποδόσεις τιµών.

Η διαδικασία επιστροφής σε εναλλακτικά µονοπάτια αναζήτησης,
ύστερα από µια αποτυχία, ονοµάζεται backtracking (υπαναχώρηση).
10
Γιάννης Ρεφανίδης
19
Στρατηγική αναζήτησης

Η στρατηγική αναζήτησης που ακολουθεί η Prolog είναι η πρώτα-
κατά-βάθος.

Για παράδειγµα, έστω η παρακάτω ερώτηση:

?- a, b.

Θα προσπαθήσει λοιπόν η Prolog να «αποδείξει» πρώτα το a και
µετά το b.

Έστω ότι στο τρέχον πρόγραµµα υπάρχει ο κανόνας:

a:-a1, a2.

Άρα, για να αποδείξει η Prolog το a, πρέπει να αποδείξει τα a1 και
a2.

Η Prolog θα προσπαθήσει να αποδείξει τα a1 και a2 πριν το b.

∆ηλαδή, τα «παιδιά» του τρέχοντος στόχου έχουν προτεραιότητα
έναντι των υπόλοιπων παλαιότερων στόχων.
Γιάννης Ρεφανίδης
20
Ανώνυµη µεταβλητή
(1/3)

Τόσο στα προγράµµατα, όσο και στις ερωτήσεις µας στην Prolog,
µπορούµε να χρησιµοποιούµε την ανώνυµη µεταβλητή.

Ως «ανώνυµη» χαρακτηρίζεται η µεταβλητή ‘_’ (χωρίς τα
εισαγωγικά) καθώς και οποιαδήποτε µεταβλητή έχει όνοµα που
ξεκινά µε το σύµβολο ‘_’.

Η εµφάνιση της ανώνυµης µεταβλητής σε µια ερώτηση, για
παράδειγµα στην:

?- male(_).

ερµηνεύεται ως εξής:

Υπάρχει κάποιο «αντικείµενο» του προγράµµατος το οποίο να είναι
male ;

Εάν υπάρχει κάποιο τέτοιο αντικείµενο, η Prolog θα αποκριθεί yes
(χωρίς να µας αναφέρει το όνοµά του), ειδάλλως θα αποκριθεί no.
11
Γιάννης Ρεφανίδης
21
Ανώνυµη µεταβλητή
(2/3)

Η ανώνυµη µεταβλητή µπορεί να εµφανίζεται και µέσα σε κανόνες.
Για παράδειγµα, έστω ότι έχουµε τον κανόνα:

parent(X,Y):- …

ο οποίος δηλώνει ότι ο Χ είναι γονιός του Υ.

Έστω ότι θέλουµε να ορίσουµε µε κανόνες το κατηγόρηµα
children_benefit(X), το οποίο ορίζει ότι ο Χ δικαιούται επίδοµα
τέκνων. Αυτό ισχύει µόνο εφόσον ο Χ είναι γονιός κάποιου Υ, χωρίς
να µας ενδιαφέρει το όνοµα του Υ.

Το γράφουµε ως εξής:

children_benefit(X) :- parent(X,_).
Γιάννης Ρεφανίδης
22
Ανώνυµη µεταβλητή
(3/3)

ΠΡΟΣΟΧΗ
: Σε περίπτωση που σε έναν κανόνα ή σε µια ερώτηση ή
ακόµη και µέσα στο ίδιο το κατηγόρηµα εµφανίζονται περισσότερες
από µια ανώνυµες µεταβλητές, αυτές θεωρούνται διαφορετικές.

Ένας απλός τρόπος να ανιχνεύουµε πότε χρειάζεται να
χρησιµοποιήσουµε µια ανώνυµη µεταβλητή είναι ο εξής:

Εάν σε ένα γεγονός ή κανόνα το όνοµα µιας µεταβλητής εµφανίζεται µία
και µόνο µια φορά, τότε αυτή η µεταβλητή µπορεί να γίνει ανώνυµη.

Τέτοιες µεταβλητές που εµφανίζονται µόνο µια φορά σε κάποιο
γεγονός ή κανόνα ονοµάζονται µοναδικές µεταβλητές (singleton
variables).

Επειδή µοναδικές µεταβλητές µπορεί να προκύψουν από ακούσιο
αναγραµµατισµό, όλες οι Prolog εµφανίζουν προειδοποιήσεις
(warnings) για αυτές κατά τη διαδικασία µετάφρασης (compile) ενός
προγράµµατος.
12
Γιάννης Ρεφανίδης
23
Σύνθετοι όροι
(1/3)

Τα ορίσµατα των κατηγορηµάτων µπορεί να είναι σύνθετοι όροι (ή
αλλιώς «συναρτησιακοί όροι»).

Ένας σύνθετος όρος αποτελείται από το συναρτησιακό σύµβολο και
τα ορίσµατα:

f(k1, k2, …, kn)

Τα ορίσµατα ενός σύνθετου όρου µπορεί να είναι και αυτά σύνθετοι
όροι.

Οι σύνθετοι όροι χρησιµεύουν στην οµαδοποίηση των ορισµάτων
ενός κατηγορήµατος και τελικά στην αναγνωσιµότητα του
προγράµµατος.

Οποιοδήποτε πρόγραµµα µε σύνθετους όρους µπορεί πολύ εύκολα να
µετατραπεί σε ένα πρόγραµµα χωρίς σύνθετους όρους.
Γιάννης Ρεφανίδης
24
Σύνθετοι όροι
(2/3)

Για παράδειγµα, έστω ότι στην αναλυτική γεωµετρία θέλουµε να
αναπαραστήσουµε ευθύγραµµα τµήµατα στο επίπεδο.

Για κάθε ευθ.τµήµα χρειάζεται να αναπαραστήσουµε τις δύο
κορυφές του, άρα χρειαζόµαστε 4 αριθµούς. Για παράδειγµα:

segment (2,1,5,4).

όπου το segment είναι ένα κατηγόρηµα τάξης 4.

Εναλλακτικά µπορούµε να οµαδοποιήσουµε τις τέσσερις
παραµέτρους του segment, χρησιµοποιώντας σύνθετους όρους. Για
παράδειγµα:

segment (point(2,1), point(5,4) ).

όπου το segment είναι πλέον ένα κατηγόρηµα τάξης 2 και έχουµε επιπλέον
ορίσει/χρησιµοποιήσει και τον συναρτησιακό όρο point µε δύο ορίσµατα.

Αυτό που κερδίσαµε είναι η αναγνωσιµότητα του προγράµµατος.
13
Γιάννης Ρεφανίδης
25
Σύνθετοι όροι
(3/3)

Προσοχή χρειάζεται όταν ενοποιούµε κατηγορήµατα που
περιλαµβάνουν σύνθετους όρους. Παρακάτω δίνονται µερικά
παραδείγµατα.
αποτυχίαsegment(3,4,5,4)segment(point(2,3),
point(5,4) )
{point(2,3)/X, 5/Y, 4/Z}segment(X, point(Y,Z))segment(point(2,3),
point(5,4) )
{point(2,3)/X, point(5,4)/Y}segment(X,Y)segment(point(2,3),
point(5,4) )
Γιάννης Ρεφανίδης
26
Αναδροµή
(1/3)

Ένας κανόνας στην Prolog που περιλαµβάνει στο σώµα
του κάποια κλήση στο κατηγόρηµα της κεφαλής του κανόνα
ονοµάζεται αναδροµικός.

Για παράδειγµα, έστω το παρακάτω πρόγραµµα:

father(nick, george).

father(george, john).

father(john, jim).

ancestor(X,Y) :- father(X,Y).

ancestor(X,Y) :- father(X,Z), ancestor(Z,Y).

Ο κανόνας που ορίζει τη σχέση ancestor είναι αναδροµικός.

?- ancestor(nick, jim).

yes
14
Γιάννης Ρεφανίδης
27
Αναδροµή
(2/3)

Χωρίς τη χρήση αναδροµικού κανόνα, θα έπρεπε να ορίσουµε
πολλούς µη αναδροµικούς κανόνες της µορφής:

ancestor(X,Y) :- father(X,Y).

ancestor(X,Y) :- father(X,Z), father(Z,Y).



ancestor(X,Y) :- father(X,Z1), father(Z1,Z2), …, father(ZN,Y).



Είναι φανερό ότι οι παραπάνω κανόνες, εκτός από το ότι είναι πάρα
πολλοί, δεν καλύπτουν και όλες τις περιπτώσεις.
Γιάννης Ρεφανίδης
28
Αναδροµή
(3/3)

Με τη χρήση αναδροµικών κανόνων, είναι δυνατόν η Prolog να
παγιδευτεί σε ατέρµονα κλαδιά αναζήτησης και να µην δώσει καµία
απάντηση (είτε θετική είτε αρνητική), όση ώρα και αν περιµένουµε.

Σε περίπτωση θετικής αναµενόµενης απάντησης, αυτό θα µπορούσε να
αποφευχθεί εάν η Prolog ακολουθούσε µια αναζήτηση πρώτα-κατά-
πλάτος (αντί για την αναζήτηση πρώτα-κατά-βάθος) που υιοθετεί.

Αλλάζοντας τη σειρά συγγραφής των κανόνων της βάσης γνώσης,
καθώς και των κλήσεων στο σώµα κάθε κανόνα, συνήθως
αποφεύγουµε τέτοια προβλήµατα.

Για παράδειγµα, ο παρακάτω αναδροµικός ορισµός της διαδικασίας
ancestor, όπου έχει αλλάξει η σειρά των δύο κανόνων αλλά και στο
σώµα του κανόνα, «κολλάει» σε οποιαδήποτε κλήση στο ancestor/2.

ancestor(X,Y) :- ancestor(Z,Y), father(X,Z).

ancestor(X,Y) :- father(X,Y).
15
Γιάννης Ρεφανίδης
29
Λίστες
(1/3)

Οι λίστες είναι η σηµαντικότερη δοµή δεδοµένων της Prolog.

Μια λίστα είναι ένας δυναµικός µονοδιάστατος πίνακας µε
στοιχεία του αριθµούς, σύµβολα, σύνθετους όρους ή και
άλλες λίστες.

Τα στοιχεία µιας λίστας δεν είναι απαραίτητο να είναι του ίδιου
τύπου.

Μερικά παραδείγµατα λιστών:

[physics, 8, maths, 9]

[ nick, 3, 12, ball, point(1,2), [a, b] ]
Γιάννης Ρεφανίδης
30
Λίστες
(2/3)

Μια λίστα µπορεί να ενοποιηθεί µε µια µεταβλητή:

Χ= [physics, 8, maths, 9]

Τις περισσότερες φορές θέλουµε να χειριστούµε τα στοιχεία µιας
λίστας ένα-ένα.

Σε αυτή την περίπτωση έχουµε τη δυνατότητα να ενοποιήσουµε
µια µεταβλητή µε το πρώτο στοιχείο της λίστας (κεφαλή) και µια
άλλη µεταβλητή µε τα υπόλοιπα στοιχεία της λίστας (ουρά).

Έτσι η προσπάθεια ταυτοποίησης:

[Χ|Y] = [physics, 8, maths, 9]

µας δίνει:

Χ = physics

Y = [8, maths, 9]
16
Γιάννης Ρεφανίδης
31
Λίστες
(3/3)

Παρακάτω φαίνονται διάφορες προσπάθειες ενοποίησης µε λίστες.
{ [a] / X, []/Y }[X | Y][[a]]
{1/X, k/Y, [[m]] /Z }[ f(X), Y| Z ][f(1), k, [m] ]
{1/X, k/Y, [m] /Z }[ f(X), Y, Z ][f(1), k, [m] ]
{ [b] / X }[a | X ][a,b]
{ b / X }[a,X][a, b]
αποτυχία[X, Y][a, b, c]
αποτυχία[a, b, c, d][a, b, c]
Ενοποίηση2ος όρος1ος όρος
Γιάννης Ρεφανίδης
32
Παρατήρηση

Οι λίστες στην Prolog µπορεί να θεωρηθεί ότι είναι σύνθετοι όροι
τάξης 2, όπου το πρώτο όρισµα είναι η κεφαλή της λίστας και το
δεύτερο η ουρά της (σε µορφή λίστας).

Ως συναρτησιακό σύµβολο χρησιµοποιείται η τελεία.

Έτσι ισχύουν οι παρακάτω ισοδυναµίες.

[a, b] = .(a, .(b) )

[a] = .(a, [ ] )

[a, b, c] = .(a, .(b, .(c, [ ] ) ) )

[ [a], b] = .( .(a, [ ] ), .(b, [ ] ) )

Ουσιαστικά δηλαδή η αναπαράσταση µε αγκύλες είναι για την
ευκολία του προγραµµατιστή και την αναγνωσιµότητα των
προγραµµάτων.
17
Γιάννης Ρεφανίδης
33
Παράδειγµα: Έλεγχος
συµπερίληψης σε λίστα

Το κατηγόρηµα member παρακάτω ελέγχει εάν ένα στοιχείο (πρώτο
όρισµα) ανήκει σε µία λίστα (το δεύτερο όρισµα):

member(X, [ X | Y ] ).

member(X, [ Head | Tail ] ) :- member(X, Tail).

Πιθανές ερωτήσεις:

?- member( 2, [ 1, 2, 3] ).

yes

?- member(4, [ 1, 2, 3 ] ).

no

?- member(X, [1,2,3]).

X=1 X=2 X=3

Κατηγορήµατα σαν το παραπάνω υπάρχουν ενσωµατεµένα στις
περισσότερες υλοποιήσεις της Prolog και έτσι δεν χρειάζεται να ορίζονται
ξανά από τον προγραµµατιστή.
Γιάννης Ρεφανίδης
34
Παράδειγµα: Εύρεση
τελευταίου στοιχείου λίστας
(1/2)

Το παρακάτω κατηγόρηµα, last(X,L) επιστρέφει ως Χ το τελευταίο
στοιχείο της λίστας L.

last(X, [X]).

last(X, [ _Head | Tail] ):- last(X,Tail).

Μπορούµε να κάνουµε κλήσεις στο last/2 µε το πρώτο όρισµα
ελεύθερο και το δεύτερο δεσµευµένο. Για παράδειγµα, µια τέτοια
κλήση είναι η:

?- last( Z, [2, 3, 4] ).

η οποία επιστρέφει

Z=4

Εάν καλέσουµε το κατηγόρηµα και µε τα δύο ορίσµατα δεσµευµένα,
η απάντηση που θα πάρουµε θα είναι yes ή no, ανάλογα µε το αν το
πρώτο όρισµα είναι το τελευταίο στοιχείο της λίστα τους δεύτερου
ορίσµατος ή όχι.
18
Γιάννης Ρεφανίδης
35
Παράδειγµα: Εύρεση
τελευταίου στοιχείου λίστας
(2/2)

Για παράδειγµα, η κλήση:

?- last( 4, [2, 3, 4]) .

επιστρέφει yes, ενώ η κλήση:

?- last( 1, [2, 3, 4]) .

επιστρέφει no.

Το κατηγόρηµα last θα λειτουργήσει ακόµη και αν το καλέσουµε µε το
πρώτο όρισµα δεσµευµένο και το δεύτερο ελεύθερο!

Σε αυτή την περίπτωση θα επιστρέψει (άπειρες στο πλήθος) λίστες των
οποίων το τελευταίο στοιχείο είναι το πρώτο όρισµα.

Μπορούµε ακόµη να το καλέσουµε και µε τα δύο ορίσµατα ελεύθερα.

Σε αυτή την περίπτωση θα επιστρέψει (άπειρες στο πλήθος) λίστες των
οποίων το τελευταίο στοιχείο είναι ενοποιηµένο µε το πρώτο όρισµα
(µολονότι και τα δύο εξακολουθούν να είναι µεταβλητές).
Γιάννης Ρεφανίδης
36
Παράδειγµα:
Συνένωση λιστών
(1/3)

Θα ορίσουµε ένα κατηγόρηµα append(L1,L2,L3), το οποίο
επιτυγχάνει όταν η λίστα L3 είναι η ένωση (παράθεση) των L1 και
L2.

Θα µπορούµε να το καλούµε µε οποιοδήποτε όρισµά του δεσµευµένο ή
ελεύθερο.

Οι βασικές ιδέες είναι οι εξής:

Η συνένωση µιας λίστας L2 µε την κενή λίστα είναι η ίδια λίστα L2.

append([ ], L2, L2).

Η συνένωση µιας λίστας ενός στοιχείου και µιας λίστας L2 δίνεται από
τον παρακάτω κανόνα:

append([A], L2, [A|L2]).

Παρατήρηση: Η παραπάνω δήλωση θα µπορούσε πιο περιφραστικά να γραφεί
ισοδύναµα και ως:
append(L1, L2, L3) :- L1=[A], L3=[A|L2].
19
Γιάννης Ρεφανίδης
37
Παράδειγµα:
Συνένωση λιστών
(2/3)

(συνέχεια...)

Για τη συνένωση µιας λίστας L1 πολλών στοιχείων µε τη λίστα L2
σκεφτόµαστε ως εξής: Αφαιρούµε από την L1 το πρώτο στοιχείο της Χ
και κάνουµε συνένωση της ουράς της L1 (έστω Τ1 το όνοµά της) µε την
L2. Στο αποτέλεσµα προσθέτουµε και το πρώτο στοιχείο.

append( L1, L2 L3 ) :- L1=[X |T1], append(T1,L2, L30), L3=[X|L30].

Ο παραπάνω κανόνας είναι αρκετά περιφρατικός. Πιο σύντοµα µπορεί να
γραφεί και ως:
append( [Χ|Τ1], L2,[Χ|L30]) :- append(T1,L2, L30).

Άρα τελικά το πρόγραµµά µας γράφεται:

append([ ], L2, L2).

append([A], L2, [A|L2]).

append( L1, L2 L3 ) :- L1=[X |T1], append(T1,L2, L30), L3=[X|L30].
Γιάννης Ρεφανίδης
38
Παράδειγµα:
Συνένωση λιστών
(3/3)

Το πρόγραµµα που βρήκαµε:

append([ ], L2, L2).

append([A], L2, [A|L2]).

append( L1, L2 L3 ) :- L1=[X |T1], append(T1,L2, L30), L3=[X|L30].

µπορεί τελικά να απλοποιηθεί στο ισοδύναµο:

append([ ], L, L).

append( [X|T1], L2, [X|L3] ) :- append(T1,L2,L3).

Ουσιαστικά η περίπτωση που η L1 έχει ένα µόνο στοιχείο δεν
χρειάζεται, µιας και µπορεί να καλυφθεί από τους δύο άλλους κανόνες.
20
Γιάννης Ρεφανίδης
39
Παράδειγµα:
∆ιαγραφή στοιχείων από λίστα
(1/2)

Θέλουµε να φτιάξουµε το κατηγόρηµα delete(X,L1,L2), όπου Χ είναι
ένα στοιχείο, L1 µια λίστα και L2 η λίστα L1 από την οποία έχουν
εµφανιστεί όλες οι εµφανίσεις του Χ.

Οι βασικές ιδέες είναι οι εξής:

Εάν η L1 είναι κενή, τότε και η L2 είναι κενή:

delete( _X, [ ] , [ ] ).

Πιο περιφραστικά µπορεί κανείς να γράψει:
delete( _X, L1 , L2] ) :- L1=[ ], L2=[ ].

Εάν το στοιχείο Χ ταυτίζεται µε το πρώτο στοιχείο της L1, τότε η L2
ισούται µε την ουρά της L1, από την οποία όµως έχουν αφαιρεθεί όλα τα
X.

delete( X, [ X | T1], L2) :- delete(X, Τ1, L2).
Γιάννης Ρεφανίδης
40
Παράδειγµα:
∆ιαγραφή στοιχείων από λίστα
(2/2)

(συνέχεια...)

Εάν τέλος το πρώτο στοιχείο της L1 δεν ταυτίζεται µε το Χ, τότε η L2
αποτελείται από το πρώτο στοιχείο της L1 και από όσα από τα
υπόλοιπα στοιχεία της L1 είναι διαφορετικά από το Χ.

delete(X, [ X1 | T1], [ X1 | T2] ) :- X \=X1, delete(X, T1, T2).

Άρα το συνολικό πρόγραµµα έχει ως εξής:

delete( _X, [ ] , [ ] ).

delete( X, [ X | T1], L2) :- delete(X, Τ1,L2).

delete(X, [ X1 | T1], [ X1 | T2] ) :- X \=X1, delete(X, T1, T2).
21
Γιάννης Ρεφανίδης
41
Παράδειγµα:
Αναστροφή λίστας

Θέλουµε να ορίσουµε το κατηγόρηµα reverse(L1, L2), το οποίο
επιτυγχάνει όταν η λίστα L1 είναι η ανάστροφη της L2.

Οι βασικές ιδέες είναι οι εξής:

Η ανάστροφη της κενής λίστας είναι η κενή λίστα.

reverse( [ ] , [ ] ) .

Για να αναστρέψουµε την L1,ξεχωρίζουµε το πρώτο στοιχείο της H1
από την ουρά της T1, αναστρέφουµε την ουρά της και έστω RT1 η
ανεστραµµένη ουρά. Τότε η L2 ισούται µε την ένωση της RT2 και του
στοιχείου H1.

reverse( [ H1 | T1], L2):- reverse(T1, RT1), append(RT1, [H1], L2).
Γιάννης Ρεφανίδης
42
Παρατηρήσεις

Πολλά από τα κατηγορήµατα χειρισµού λιστών που είδαµε στις
προηγούµενες διαφάνειες υπάρχουν ενσωµατωµένα στις
περισσότερες υλοποιήσεις της γλώσσας Prolog.

Σε αυτές τις περιπτώσεις καλό είναι να χρησιµοποιούµε τα
υπάρχοντα κατηγορήµατα, τα οποία είναι πιο αποτελεσµατικά.

Όσον αφορά τις δικές µας υλοποιήσεις, αυτές που παρουσιάστηκαν
στις προηγούµενες διαφανειες δεν είναι οι µοναδικές. Θα µπορούσε
κανείς να βρει και εναλλακτικές ισοδύναµες υλοποιήσεις, οι οποίες
ενδεχοµένως να ήταν αποτελεσµατικότερες (δηλ. γρηγορότερες).
22
Γιάννης Ρεφανίδης
43
Αριθµητικές διαδικασίες
(1/4)

Ως ορίσµατα κατηγορηµάτων µπορούν να χρησιµοποιηθούν και
αριθµοί.

Μπορούµε µάλιστα να κατασκευάζουµε αριθµητικές εκφράσεις
χρησιµοποιώντας τις γνωστές πράξεις +, -, *, /, καθώς και
συναρτήσεις όπως sin(), cos(), exp() κλπ.

Μια µεταβλητή µπορεί να ενοποιηθεί µε έναν αριθµό ή µία
αριθµητική έκφραση.

Μπορούµε να συγκρίνουµε αριθµούς/ αριθµητικές εκφράσεις (που
περιέχουν µεταβλητές) χρησιµοποιώντας τους παρακάτω τελεστές
(κατηγορήµατα), µε τη γνωστή τους σηµασία:

>, >=, <, =<, =:= και =/= .
Γιάννης Ρεφανίδης
44
Αριθµητικές διαδικασίες
(2/4)

Προσοχή χρειάζεται στη διάκριση των τελεστών =:= και =. Ο απλός
τελεστής = συγκρίνει όρους προσπαθώντας να εκτελέσει ενοποίηση.
Για παράδειγµα, η κλήση:

parent(X,Y) = parent(nick, maria)

επιτυγχάνει.

Αντίθετα οι κλήσεις:

2+1 = 1+2

2+3 = 1+4

αποτυγχάνουν.

Πετυχαίνει όµως η κλήση:

2+1 = 2+1

θεωρώντας την έκφραση 2+1 ως τον σύνθετο όρο +(2,1).
23
Γιάννης Ρεφανίδης
45
Αριθµητικές διαδικασίες
(3/4)

Για σύγκριση αριθµητικών εκφράσεων µε ταυτόχρονη εκτέλεση
πράξεων χρησιµοποιούµε τον τελεστή =:=, ο οποίος πετυχαίνει σε
όλες τις παρακάτω περιπτώσεις:

2+1 =:= 1+2

2+3 =:= 1+4

2+1 =:= 2+1

Παρόµοια, για να ελέγξουµε εάν δύο αριθµητικές εκφράσεις
καταλήγουν σε διαφορετικό αριθµητικό αποτέλεσµα χρησιµοποιούµε
τον τελεστή =\= .

Ο αντίστοιχος τελεστής\= ελέγχει εάν δύο όροι δεν είναι ενοποιήσιµοι,
χωρίς να εκτελέσει καµία πράξη.

ΠΡΟΣΟΧΗ: Όλοι οι αριθµητικοί τελεστές ισότητας/ανισότητας
απαιτούν κατά την κλήση τους όλες οι µεταβλητές που εµπλέκονται
στις αριθµητικές εκφράσεις να είναι δεσµευµένες.
Γιάννης Ρεφανίδης
46
Αριθµητικές διαδικασίες
(4/4)

Για να εκτελέσουµε µια αριθµητική πράξη και να αποδώσουµε το
αποτέλεσµα σε µια µεταβλητή χρησιµοποιούµε τον τελεστή is :

X is 2+3

όπου το αποτέλεσµα είναι η µεταβλητή Χ να πάρει την τιµή 5.

Αντίθετα αν χρησιµοποιήσουµε τον τελεστή =, δηλαδή:

Χ=2+3

το αποτέλεσµα θα είναι η µεταβλητή Χ να πάρει ως τιµή το σύνθετο
όρο 2+3.

Ή σε προθεµατική µορφή: +(2,3).
24
Γιάννης Ρεφανίδης
47
Ενσωµατωµένα κατηγορήµατα
(1/5)

Μέχρι τώρα είδαµε προγράµµατα της Prolog, όπου όλα τα
κατηγορήµατα ορίστηκαν από τον χρήστη.

Η Prolog διαθέτει όµως πολλά ενσωµατωµένα κατηγορήµατα, τα
οποία επιτελούν ειδικές λειτουργίες.

Για παράδειγµα, το κατηγόρηµα member(X,Y) επιστρέφει yes όταν
το Y είναι λίστα και το Χ είναι στοιχείο αυτής της λίστας, ειδάλλως
επιστρέφει no.
Γιάννης Ρεφανίδης
48
Ενσωµατωµένα κατηγορήµατα
(2/5)

Μπορούµε να χωρίσουµε τα ενσωµατωµένα κατηγορήµατα σε δύο
οµάδες:

Αυτά που έχουν λογική σηµασία: Επιστρέφουν yes ή no, ανάλογα µε
την περίπτωση.

Παράδειγµα είναι το member(X,Y).

Αυτά που έχουν διαδικαστική σηµασία: Εκτελούν µια συγκεκριµένη
δουλειά και επιστρέφουν (σχεδόν) πάντα yes.

Παράδειγµα: Το κατηγόρηµα write(Χ) τυπώνει τον όρο Χ στην οθόνη και
επιστρέφει yes. Παρόµοια το κατηγόρηµα nl αλλάζει παράγραφο στην
οθόνη.
25
Γιάννης Ρεφανίδης
49
Ενσωµατωµένα κατηγορήµατα
(3/5)

Μπορούµε να διακρίνουµε τα ορίσµατα των ενσωµατωµένων
κατηγορηµάτων σε δύο κατηγορίες:

Αυτά που κατά την κλήση του κατηγορήµατος πρέπει να έχουν πάρει
τιµή.

Για παράδειγµα, η κλήση Χ>Υ θα προκαλέσει τερµατισµό προγράµµατος,
εάν ένα από τα δύο ορίσµατα του κατηγορήµατος'>' δεν έχει πάρει τιµή.

Αυτά που κατά την κλήση του κατηγορήµατος δεν είναι υποχρεωτικό να
έχουν πάρει τιµή.

Για παράδειγµα, η κλήση member(X,[ 1 2 3]), εάν το Χ δεν έχει πάρει
τιµή, θα επιστρέψει διαδοχικά: Χ=1, Χ=2 και Χ=3.
Γιάννης Ρεφανίδης
50
Ενσωµατωµένα κατηγορήµατα
(4/5)

Παρακάτω αναφέρουµε µερικά από τα ενσωµατωµένα
κατηγορήµατα που βρίσκουµε στις περισσότερες Prolog, µαζί µε µια
σύντοµη ερµηνεία τους.

get(X): ∆ιαβάζει από το πληκτρολόγιο τον επόµενο χαρακτήρα που
πληκτρολογεί ο χρήστης και τον επιστρέφει στη µεταβλητή Χ.

read(X): ∆ιαβάζει από το πληκτρολόγιο τον επόµενο όρο που
πληκτρολογεί ο χρήστης και τον επιστρέφει στη µεταβλητή Χ.

put(X): Τυπώνει στην οθόνη τον χαρακτήρα Χ.

write(X): Τυπώνει στην οθόνη τον όρο Χ.

nl: Αλλάζει γραµµή στην οθόνη.
26
Γιάννης Ρεφανίδης
51
Ενσωµατωµένα κατηγορήµατα
(5/5)

Ο χειρισµός (ανάγνωση και εγγραφή) αρχείων δίσκου γίνεται µε τις
ίδιες εντολές που γίνεται η είσοδος/έξοδος από/σε
πληκτρολόγιο/οθόνη.

Χρησιµοποιούνται ειδικές εντολές για να ανακατευθύνουν την είσοδο
και την έξοδο από και σε αρχείο. Ειδικότερα:

see(Stream): Στο εξής η είσοδος διαβάζεται από το Stream.

seen : Στο εξής είσοδος γίνεται ξανά το πληκτρολόγιο και όλα τα ανοικτά
αρχεία εισόδου κλείνουν.

seeing(Stream): Επιστρέφει στη µεταβλητή Stream το όνοµα του
τρέχοντος ρεύµατος εισόδου.

tell(Stream): Το Stream γίνεται το τρέχον ρεύµα εξόδου.

told: Τρέχον ρεύµα εξόδου γίνεται η οθόνη και όλα τα ανοικτά αρχεία
εξόδου κλείνουν.

telling(Stream): Επιστρέφει στη µεταβλητή Stream το όνοµα του
τρέχοντος ρεύµατος εξόδου.
Γιάννης Ρεφανίδης
52
Αποκοπή
(1/3)

Έχουµε ορίσει ως διαδικασία ένα σύνολο κανόνων
(συµπεριλαµβανοµένων και γεγονότων) που έχουν το ίδιο
κατηγόρηµα στην κεφαλή τους.

Για παράδειγµα, οι παρακάτω κανόνες αποτελούν µια διαδικασία:

uncle(X,Y) :-
brother(X,Z),
father(Z,Y).

uncle(X,Y):-
brother(X,Z),
mother(Z,Y).

η οποία ορίζει πότε ο Χ είναι θείος του/της Υ.
27
Γιάννης Ρεφανίδης
53
Αποκοπή
(2/3)

Έστω επίσης ότι έχουµε τα παρακάτω γεγονότα:

brother(bob, john).

brother(bob, nick).

father(john,ann).

Θέλουµε να ελέγξουµε εάν ο bob είναι θείος της ann. Εκτελούµε
λοιπόν την ερώτηση:

uncle(bob,ann).

Στον πρώτο κανόνα της διαδικασίας uncle/2 η παραπάνω ερώτηση
πετυχαίνει, µε τις ενοποιήσεις X=bob, Y=ann και Z=john.

Η διαδικασία επιστρέφει επιτυχώς, ωστόσο θυµάται ότι πρέπει
κάποια στιγµή στο µέλλον να ελέγξει και το δεύτερο κανόνα της
διαδικασίας uncle.
Γιάννης Ρεφανίδης
54
Αποκοπή
(3/3)

Με την αποκοπή µπορούµε να καθορίσουµε ότι εάν επιτύχει ο πρώτος
κανόνας της διαδικασίας uncle/2 δεν χρειάζεται να ελεγχθεί και ο δεύτερος.

Αυτό γίνεται ως εξής:

uncle(X,Y) :-
brother(X,Z),
father(Z,Y),
!.

uncle(X,Y):-
brother(X,Z),
mother(Z,Y).

Γενικά όταν η Prolog συναντήσει µια αποκοπή, δεν ελέγχει τους επόµενους
κανόνες της τρέχουσας διαδικασίας, ενώ «ξεχνά» όλα τα εναλλακτικά
µονοπάτια που ενδεχοµένως αποµένει να ελέγξει από προηγούµενες
κλήσεις της ίδιας διαδικασίας (στην προκειµένη περίπτωση εναλλακτικές
κλήσεις στα borher(X,Z) και father(Z,Y), καθώς και από προηγούµενους
κανόνες της uncle/2, εφόσον υπήρχαν).
28
Γιάννης Ρεφανίδης
55
Παράδειγµα αποκοπής:
Βηµατική συνάρτηση

Έστω ότι θέλουµε να ορίσουµε το κατηγόρηµα step(X,Y), το οποίο
προσοµοιώνει τη βηµατική συνάρτηση: Εάν το Χ είναι µικρότερο από
το 0, το Υ είναι µηδέν, ειδάλλως το Υ είναι 1.

Η έκδοση του προγράµµατος χωρίς αποκοπή είναι η εξής:

step(X,0):- X<0.

step(X,1):- X>=0.

Η έκδοση του προγράµµατος µε αποκοπή είναι η εξής:

step(X,0):- X<0, !.

step(X,1).

Η πρώτη έκδοση ελέγχει πάντα και τον δεύτερο κανόνα, ακόµη και αν
έχει επιτύχει ο πρώτος.

Η δεύτερη έκδοση ελέγχει τον δεύτερο κανόνα µόνο αν αποτύχει ο
πρώτος. Μάλιστα, σε αυτή την περίπτωση ο δεύτερος κανόνας έχει
µετατραπεί σε απλό γεγονός! (δηλαδή αφαιρέθηκε ο ανισοτικός
έλεγχος).
Γιάννης Ρεφανίδης
56
Παράδειγµα αποκοπής:
Η διαδικασία member
(1/3)

Ορίσαµε σε προηγούµενη διαφάνεια τη διαδικασία member(X,L), η
οποία επιτυγχάνει όταν το Χ είναι µέλος της λίστας L.

Το πρόγραµµα που παρουσιάσαµε ήταν το εξής:

member(X, [ X | Y ] ).

member(X, [ Head | Tail ] ) :- member(X, Tail).

Έστω ότι κάνουµε την ερώτηση:

?- member(2, [1,2,3,4]).

Η παραπάνω διαδικασία λειτουργει ως εξής:

Ο πρώτος κανόνας συγκρίνει το 2 µε το πρώτο στοιχείο της λίστας.
Επειδή δεν ταυτίζονται αποτυγχάνει.

Ο δεύτερος κανόνας καλεί αναδροµικά τη διαδικασία, έχοντας αφαιρέσει
το πρώτο στοιχείο της λίστας.

Η αναδροµική κλήση πετυχαίνει αµέσως και έτσι πετυχαίνει και η αρχική
κλήση.
29
Γιάννης Ρεφανίδης
57
Παράδειγµα αποκοπής:
Η διαδικασία member
(2/3)

Αφού η διαδικασία επιστρέψει επιτυχώς, διατηρεί στη µνήµη
εναλλακτικά µονοπάτια απόδειξης.

Τα µονοπάτια αυτά προκύπτουν από το γεγονός ότι η πρώτη
αναδροµική κλήση µπορούσε ενδεχοµένως να ικανοποιηθεί από τον
δεύτερο κανόνα, δηλαδή πάλι αναδροµικά, κοκ.

Ουσιαστικά η διαδικασία θα συνεχίσει να ελέγχει εάν το 2 υπάρχει στη
λίστα [1,2,3,4] µέχρι να ελέγξει ολόκληρη τη λίστα.

Ενδεχοµένως θα θέλαµε η διαδικασία member να τερµατίζει µόλις
ανακαλύψει την πρώτη εµφάνιση του στοιχείου που αναζητούµε στη
λίστα. Αυτό επιτυγχάνεται µε το παρακάτω πρόγραµµα:

member(X, [ X | Y ] ):-! .

member(X, [ Head | Tail ] ) :- member(X, Tail).
Γιάννης Ρεφανίδης
58
Παράδειγµα αποκοπής:
Η διαδικασία member
(3/3)

Η προσθήκη της αποκοπής στον πρώτο κανόνα της member/2
αυξάνει την αποτελεσµατικότητα του κανόνα, έχει όµως ένα πολύ
σοβαρό µειονέκτηµα:

Εάν υποβάλλουµε την ερώτηση:

?- member(X, [1,2,3,4]).

παίρνουµε µόνο την απάντηση

Χ=1

Αντίθετα, εάν δεν υπήρχε η αποκοπή, η ίδια ερώτηση θα έδινε
διαδοχικά τις απαντήσεις:

Χ=1

Χ=2

Χ=3

Χ=4
30
Γιάννης Ρεφανίδης
59
Παρατηρήσεις

Η χρήση της αποκοπής:

Αυξάνει την αποτελεσµατικότητα των προγραµµάτων όταν οι κλήσεις
µας γίνονται χωρίς µεταβλητές.

∆εν µας επιστρέφει όλες τις εναλλακτικές λύσεις όταν οι κλήσεις γίνονται
µε µεταβλητές.

Γενικότερα, θεωρείται ότι η χρήση της αποκοπής περιορίζει τη
δηλωτικότητα (declarativeness) των προγραµµάτων, καθιστώντας
τα πιο διαδικαστικά (procedural).

Η διαδικασία member υλοποιείται από όλες τις Prolog χωρίς
αποκοπή.
Γιάννης Ρεφανίδης
60
Η άρνηση στην Prolog

Είδαµε ότι µε τις προτάσεις Horn δεν µπορούµε να αποδεικνύουµε
αρνητικές προτάσεις.

Ωστόσο, η Prolog επιτρέπει τη χρήση άρνησης στο σώµα των
κανόνων, µε χρήση της δεσµευµένης λέξης not:

alive(X) :- not dead(X).

Η λέξη not µπορεί να εµφανίζεται µόνο στο σώµα των κανόνων (όχι
δηλαδή στην κεφαλή), και ερµηνεύεται ως εξής:

Εάν µε βάση όσα γνωρίζει το πρόγραµµα ως τώρα δεν µπορέσει να
αποδείξει το dead(X), τότε µπορεί να υποθέσει ότι ισχύει το not dead(X).

Η παραπάνω προσέγγιση ονοµάζεται"υπόθεση του κλειστού
κόσµου", αφού υποθέτει ότι γνωρίζουµε τα πάντα σε σχέση µε το
συγκεκριµένο πρόβληµα, άρα ό,τι δεν µπορούµε να το αποδείξουµε
δεν ισχύει.
31
Γιάννης Ρεφανίδης
61
∆υναµική τροποποίηση
προγράµµατος
(1/2)

Μπορούµε να προσθέτουµε και να αφαιρούµε γεγονότα και κανόνες
από τη µνήµη κατά την εκτέλεση του προγράµµατος.

Αυτό επιτυγχάνεται µε τα παρακάτω κατηγορήµατα:

assert( Clause ) : Προσθέτει στη µνήµη το γεγονός ή τον κανόνα Clause.

assert( city(Thessaloniki) ).

assert( (brother(X,Y):- father(Z,X), father(Z,Y), male(X) ) ).

Η πρόταση Clause τοποθετείται ύστερα από όλες τις προτάσεις του ίδιου
κατηγορήµατος.

asserta( Clause ) : Ίδιο µε το assert/1,µε µοναδική διαφορά ότι
προσθέτει την πρόταση Clause πριν από άλλες προτάσεις του ίδιου
κατηγορήµατος.

retract( Clause ) : ∆ιαγράφει από τη µνήµη το πρώτο γεγονός ή κανόνα
που ταυτοποιείται µε το Clause.

retract_all( Clause ) : ∆ιαγράφει από τη µνήµη όλα τα γεγονότα/
κανόνες που ταυτοποιούνται µε το Clause.
Γιάννης Ρεφανίδης
62
∆υναµική τροποποίηση
προγράµµατος
(2/2)

Η δυνατότητα δυναµικής τροποποίησης ενός προγράµµατος µας
επιτρέπει, µεταξύ άλλων, να έχουµε καθολικές µεταβλητές στην
Prolog.

Για παράδειγµα, έστω ότι χρειαζόµαστε έναν καθολικό µετρητή, ο
οποίος θα ενηµερώνεται από διάφορα σηµεία του προγράµµατος.

Μπορούµε να δηλώσουµε ένα γεγονός:

counter(0).

Στη συνέχεια, εάν σε κάποιο σηµείο του προγράµµατος θέλουµε να
αυξήσουµε την τιµή του κατά 1, προσθέτουµε τις παρακάτω εντολές:

...
retract( counter(X) ),
X1 is X + 1,
assert( counter(X1) ),

32
Γιάννης Ρεφανίδης
63
Παρατήρηση

Προσοχή: Για να µπορούµε να προσθέτουµε/ αφαιρούµε δυναµικά
προτάσεις που αφορούν ένα συγκεκριµένο κατηγόρηµα, αυτό το
κατηγόρηµα πρέπει να δηλωθεί ως δυναµικό.

Αυτό γίνεται µε δήλωση στην αρχή του προγράµµατος:

:-dynamic όνοµα_κατηγορήµατος/τάξη.

Στο παράδειγµα της προηγούµενης διαφάνειας, θα έπρεπε στην
αρχή του προγράµµατος να υπάρχει η δήλωση:

:-dynamic counter/1.

Για όσα κατηγορήµατα δεν υπάρχει η παραπάνω δήλωση, η Prolog
τα θεωρεί στατικά και δεν µας επιτρέπει να προσθέτουµε και να
αφαιρούµε προτάσεις δυναµικά κατά την εκτέλεση του
προγράµµατος.
Γιάννης Ρεφανίδης
64
Συγκέντρωση λύσεων
(1/2)

Πολλές φορές χρειαζόµαστε να µαζέψουµε σε µία λίστα όλες τις
πιθανές απαντήσεις µιας ερώτησης.

Για παράδειγµα, έστω το παρακάτω πρόγραµµα:

father(nick, george)

father(nick, mary)

father(john, elen)

Έστω ότι θέλουµε να βρούµε τα παιδιά του nick. Η ερώτηση:

?- father(nick, X)

µας επιστρέφει ένα-ένα τα παιδιά του, χωρίς να µας δίνει τη
δυνατότητα να τα χειριστούµε όλα µαζί ταυτόχρονα.
33
Γιάννης Ρεφανίδης
65
Συγκέντρωση λύσεων
(2/2)

Μπορούµε να ζητήσουµε από την Prolog να µας επιστρέψει
ταυτόχρονα όλες τις δυνατές απαντήσεις σε µια ερώτηση. Αυτό
γίνεται µε το κατηγόρηµα findall/3:

findall( Term, Goal, List ) : Κατά την κλήση της findall, η Prolog βρίσκει
όλες τις πιθανές απαντήσεις στην ερώτηση Goal. Για κάθε απάντηση
που βρίσκει τοποθετει στο List ένα αντίγραφο του Term, µε τις
µεταβλητές του Term να έχουν πάρει τιµές από την εκάστοτε απάντηση
του Goal.

Για παράδειγµα:

?- findall( X, father(nick,X), List).

List= [george, mary]

Εναλλακτικά:

findall( child(X), father(nick,X), List).

List= [ child(george), child(mary) ]
Λογικός
Προγραµµατισµός
Η γλώσσα προγραµµατισµού Prolog
 Αλγόριθµοι αναζήτησης σε Prolog
34
Γιάννης Ρεφανίδης
67
Το πρόβληµα του ίππου

Στις επόµενες διαφάνειες θα χρησιµοποιήσουµε την Prolog για να
λύσουµε προβλήµατα µε χρήση αλγορίθµων αναζήτησης.

Θα χρησιµοποιήσουµε ως παράδειγµα το πρόβληµα του ίππου στο
σκάκι, το οποίο ορίζεται ως εξής:

∆εδοµένης µια σκακιέρας ΝxN και ενός ίππου σε κάποια θέση της
σκακιέρας, να βρεθεί µια διαδροµή του ίππου µε την οποία αυτός
επισκέπεται όλα τα τετράγωνα της σκακιέρας, µια φορά το καθένα,
ξεκινώντας από συγκεκριµένη θέση (π.χ. µια γωνία).

Το πρόβληµα έχει λύση για σκακιέρες µε µήκος πλευράς
τουλάχιστον 5.
Γιάννης Ρεφανίδης
68
Αναπαράσταση θέσεων

Θα χρησιµοποιήσουµε σύνθετους όρους της µορφής pos(X,Y) για
να αναφερόµαστε στη θέση X,Y της σκακιέρας.

Για παράδειγµα, pos(1,1), pos(2,3) κλπ

Ορίσουµε τη µέγιστη διάσταση της σκακιέρας µε το γεγονός:

size(5).
54321

1
2
3
4
5
35
Γιάννης Ρεφανίδης
69
Αναπαράσταση κινήσεων
(1/4)

Οι επιτρεπτές κινήσεις του ίππου από ένα τυχαίο τετράγωνο είναι το
πολύ 8.

Για όσους δεν γνωρίζουν σκάκι, κάθε κίνηση του ίππου αντιστοιχεί σε
µετακίνησή του κατά ένα τετράγωνο στη µία διάσταση και δύο τετράγωνα
στην άλλη, αρκεί να µην βγει έξω από την σκακιέρα.

Φυσικά στο πραγµατικό σκάκι η νέα θέση δεν πρέπει να κατέχεται από
πιόνι του ίδιου χρώµατος, ενώ η µετακίνηση του ίππου δεν θα πρέπει να
αφήνει εκτεθειµένο τον βασιλιά του ίδιου χρώµατος.

Στην εικόνα δίπλα ο µαύρος ίππος
µπορεί να µετακινηθεί σε οποια-
δήποτε από τις θέσεις που είναι
σηµειωµένες.
54321
✓✓
1
✓✓
2

3
✓✓
4
✓✓
5
Γιάννης Ρεφανίδης
70
Αναπαράσταση κινήσεων
(2/4)

Ο παρακάτω κανόνας µας δίνει µια από τις επιτρεπτές κινήσεις του
ίππου:

move( pos(X,Y), pos(X1,Y1) ) :-
X>2,
Y>1,
X1 is X-2,
Y1 is Y-1.

Ο παρακάτω κανόνας µας δίνει ακόµη µια µετακίνηση προς την
αντίθετη κατεύθυνση.

move( pos(X,Y), pos(X1,Y1) ) :-
X1 is X+2,
Y1 is Y+1,
size(S),
Χ1=<S,
Υ1=<S.
36
Γιάννης Ρεφανίδης
71
Αναπαράσταση κινήσεων
(3/4)

Ακολουθούν οι ορισµοί των υπολοίπων έξι κινήσεων.

move( pos(X,Y), pos(X1,Y1) ) :-
X>1,
Y>2,
X1 is X-1,
Y1 is Y-2.

move( pos(X,Y), pos(X1,Y1) ) :-
X>1,
X1 is X-1,
Y1 is Y+2,
size(S), Υ1=<S.

move( pos(X,Y), pos(X1,Y1) ) :-
X>2,
X1 is X-2,
Y1 is Y+1,
size(S), Υ1=<S.
Γιάννης Ρεφανίδης
72
Αναπαράσταση κινήσεων
(4/4)

(συνέχεια...)

move( pos(X,Y), pos(X1,Y1) ) :-
X1 is X+1,
size(S), Χ1=<S,
Υ>2,
Y1 is Y-2.

move( pos(X,Y), pos(X1,Y1) ) :-
X1 is X+2,
size(S), Χ1=<S,
Υ>1,
Y1 is Y-1.

move( pos(X,Y), pos(X1,Y1) ) :-
X1 is X+1,
Y1 is Y+2,
size(S),
Χ1=<S,
Υ1=<S.
37
Γιάννης Ρεφανίδης
73
Αναπαράσταση σκακιέρας
(1/3)

Θα χρειαστούµε έναν τρόπο να αναπαριστούµε τη σκακιέρα, ώστε
να θυµόµαστε ποια τετράγωνα δεν έχουµε επισκεφθεί ακόµη ή ποια
έχουµε επισκεφθεί.

Ο πιο απλός τρόπος είναι µια λίστα, για παράδειγµα:

[ pos(1,1), pos(1,2), …, pos(2,1), …, pos(5,5) ]

Για ευκολότερη δηµιουργία της λίστας, µπορούµε να φτιάξουµε µια
διαδικασία που θα επιστρέφει την παραπάνω λίστα, όταν της
δώσουµε τη διάσταση της σκακιέρας.

Η διαδικασία gen_table/1 στις επόµενες διαφάνειες επιστρέφει στη
µεταβλητή Table έναν πίνακα µε στοιχεία του όλες τις θέσεις της
σκακιέρας.

Η διάσταση της σκακιέρας λαµβάνεται από το γεγονός size(5).
Γιάννης Ρεφανίδης
74
Αναπαράσταση σκακιέρας
(2/3)

gen_table(Table):-
size(S),
gen_table(1,S,Table).

gen_table(S, S, Table ):-
gen_row(S, S, Table, 1),
!.

gen_table(Row,S,Table):-
gen_row(Row, S, TableRow, 1),
Next_Row is Row+1,
gen_table(Next_Row, S, Rest_Table),
append(TableRow, Rest_Table, Table).
38
Γιάννης Ρεφανίδης
75
Αναπαράσταση σκακιέρας
(3/3)

gen_row(Row, S, [ pos(Row,S)], S) :-!.

gen_row(Row, S, [ pos(Row,Col) | Rest], Col ):-
Next_Col is Col + 1,
gen_row(Row, S, Rest, Next_Col) .
Γιάννης Ρεφανίδης
76
Αναπαράσταση καταστάσεων

Σε κάθε κατάσταση του χώρου αναζήτησης θα πρέπει να
γνωρίζουµε:

Σε ποια θέση βρισκόµαστε.

Ποιες θέσεις δεν έχουµε ακόµη επισκεφθεί.

Μπορούµε να αναπαραστήσουµε καταστάσεις µε τον παρακάτω
σύνθετο όρο:

state(Current_pos, Rest)

όπου

Current_pos είναι η τρέχουσα θέση, π.χ. pos(1,1).

Rest είναι µια λίστα µε τις θέσεις που αποµένει να επισκεφθούµε.

Για παράδειγµα:
state(pos(4,2), [pos(1,2),…,pos(5,5)] )
39
Γιάννης Ρεφανίδης
77
Τελεστής µετάβασης

Χρειαζόµαστε µια διαδικασία η οποία για κάθε κατάσταση βρίσκει,
ένα-ένα, όλα τα παιδιά της.

next_state( state(Current_pos, Rest), state(New_pos, New_rest):-
move(Current_pos, New_pos),
member(New_pos, Rest),
delete(New_pos,Rest,New_rest).
Γιάννης Ρεφανίδης
78
Αναζήτηση πρώτα κατά βάθος
(1/3)

Σε κάθε κατάσταση κατά την αναζήτηση θα πρέπει:

Να βρίσκουµε όλες τις δυνατές καταστάσεις-παιδιά.

Να επιλέγουµε µια κατάσταση-παιδί και να συνεχίζουµε από αυτήν.

Να θυµόµαστε από ποιες θέσεις περάσαµε µέχρι να φτάσουµε στην
τρέχουσα θέση.

Θα υλοποιήσουµε τον αλγόριθµο αναζήτησης πρώτα κατά βάθος
βασιζόµενοι στον ενσωµατωµένο µηχανισµό αναζήτησης της
Prolog, ο οποίος λειτουργεί µε την στρατηγική πρώτα κατά βάθος.
40
Γιάννης Ρεφανίδης
79
Αναζήτηση πρώτα κατά βάθος
(2/3)

Η διαδικασία dfs_tour/2 υλοποιεί τον αλγόριθµο αναζήτησης πρώτα
κατά βάθος:

dfs_tour( state(Current_pos, [ ] ), [Current_pos] ) .

dfs_tour( state(Current_pos, Rest), [ Current_pos | Path ] ):-
next_state(state(Current_pos, Rest), state(New_pos, New_rest) ),
dfs_tour( state(New_pos, New_rest), Path ).

Το πρώτο όρισµα της dfs_tour/2 είναι η τρέχουσα κατάσταση,
συµπεριλαµβανοµένης της τρέχουσας θέσης και των θέσεων που
αποµένει να επισκεφθούµε.

Στο δεύτερο όρισµα της dfs_tour/2 επιστρέφονται οι θέσεις που
επισκέπτεται ο αλγόριθµος, διαταγµένες µε τη σειρά επίσκεψης.

Καλούµε την dfs_tour/2 ως:

dfs_tour( state(pos(1,1), [pos(1,2), pos(1,3), …, pos(5,5)] ) , Path).
Γιάννης Ρεφανίδης
80
Αναζήτηση πρώτα κατά βάθος
(3/3)

Για πιο εύκολη κλήση της dfs_tour/2 ορίζουµε τη βοηθητική
διαδικασία run_dfs/1 ως εξής:

run_dfs(Path):-
gen_table(Table),
delete(pos(1,1),Table,Rest),
dfs_tour(state(pos(1,1),Rest), Path).

Παρακάτω φαίνεται η λύση που επιστρέφει ο αλγόριθµος:

Path=[pos(1, 1), pos(3, 2), pos(5, 3), pos(4, 1), pos(2, 2), pos(1, 4),
pos(3, 5), pos(5, 4), pos(3, 3), pos(4, 5), pos(2, 4), pos(1, 2), pos(3, 1),
pos(5, 2), pos(4, 4), pos(2, 5), pos(1, 3), pos(2, 1), pos(4, 2), pos(2, 3),
pos(1, 5), pos(3, 4), pos(5, 5), pos(4, 3), pos(5, 1)]
41
Γιάννης Ρεφανίδης
81
Παρατηρήσεις

Όπως έχουµε δει στους αλγορίθµους αναζήτησης, κάθε αλγόριθµος
αναζήτησης πρέπει να διατηρεί το µέτωπο αναζήτησης, µια λίστα
όπου υπάρχουν οι καταστάσεις εκείνες που έχουν συναντηθεί αλλά
δεν έχουν επεκταθεί ακόµη.

Στην υλοποίηση του αλγορίθµου αναζήτησης πρώτα κατά βάθος
που παρουσιάστηκε στις προηγούµενες διαφάνειες, δεν χρειάστηκε
το πρόγραµµά µας να υλοποιήσει το µέτωπο αναζήτησης.

Αντίθετα, χρησιµοποιήσαµε το εσωτερικό «µέτωπο αναζήτησης» της
Prolog, το οποίο λειτουργεί µε τη στρατηγική πρώτα-κατά-βάθος.

Εάν θελήσουµε όµως να υλοποιήσουµε οποιονδήποτε άλλο
αλγόριθµο, θα πρέπει να υλοποιήσουµε δικό µας µέτωπο
αναζήτησης.
Γιάννης Ρεφανίδης
82
Αναζήτηση πρώτα κατά πλάτος
(1/5)

Για να υλοποιήσουµε την αναζήτηση πρώτα κατά πλάτος, πρέπει να
κατασκευάσουµε µόνοι µας το µέτωπο αναζήτησης του αλγορίθµου, ως
µια λίστα καταστάσεων που έχει επισκεφθεί ο αλγόριθµος αλλά δεν έχει
ακόµη βρει τα παιδιά τους.

Λόγω της υλοποίησης της ατζέντας, χρειάζεται σε κάθε κατάσταση να
προσθέσουµε και τη διαδροµή µε την οποία ο ίππος έφθασε στην
τρέχουσα θέση.

Έτσι η περιγραφή µιας κατάστασης γίνεται:

state(Current_pos, Rest, Path)

όπου

Current_pos είναι η τρέχουσα θέση, π.χ. pos(1,1).

Rest είναι µια λίστα µε τις θέσεις που αποµένει να επισκεφθούµε.

Path είναι η λίστα των θέσεων από τις οποίες έχει περάσει ο ίππος.
42
Γιάννης Ρεφανίδης
83
Αναζήτηση πρώτα κατά πλάτος
(2/5)

Θεωρούµε ότι το µέτωπο αναζήτησης παριστάνεται ως ένα γεγονός
της µορφής:

agenda( [state(..), state(…), …, state(…) ] ).

Αρχικά το µέτωπο αναζήτησης πρέπει να περιέχει µόνο την αρχική
κατάσταση:

agenda( [ state( pos(1,1), [ pos(1,2), ..., pos(5,5) ], [pos(1,1)] ) ] ).

Το πρόγραµµα θα πρέπει, σε κάθε βήµα, να αφαιρεί από το µέτωπο
αναζήτησης την πρώτη κατάσταση, να βρίσκει τα παιδιά της, και να
τα προσθέτει στο τέλος του µετώπου αναζήτησης.

Η τροποποίηση του παραπάνω γεγονότος είναι δυνατή µε τις
εντολές retract και assert.
Γιάννης Ρεφανίδης
84
Αναζήτηση πρώτα κατά πλάτος
(3/5)

Η διαδικασία του αλγορίθµου αναζήτησης πρώτα κατά πλάτος είναι
η εξής:

bfs_tour(Path):-
retract( agenda( [ state( Current_pos, [ ] , Path ) | _ ] )),
!.

bfs_tour(Path):-
retract( agenda( [ State | Rest_agenda ] )),
find_children(State, Children),
append(Rest_agenda, Children, New_agenda),
assert( agenda(New_agenda) ),
bfs_tour(Path).

Ο πρώτος κανόνας επιτυγχάνει όταν η πρώτη κατάσταση στην
agenda έχει επισκεφθεί όλες τις θέσεις.

Ο δεύτερος κανόνας σβήνει (αφού τη διαβάσει πρώτα) την παλιά
agenda, βρίσκει τα παιδιά της πρώτης κατάστασης και τα προσθέτει
στο τέλος της νέας agenda.
43
Γιάννης Ρεφανίδης
85
Αναζήτηση πρώτα κατά πλάτος
(4/5)

Παρακάτω ορίζουµε τη διαδικασία find_children, η οποία
επιστρέφει σε λίστα όλα τα παιδιά µιας κατάστασης.

find_children(Current_state,Children):-
findall( Next_state,
next_state(Current_state, Next_state),
Children).

όπου η διαδικασία next_state έχει πλέον γίνει ως εξής:

next_state( state(Current_pos, Rest, Path),
state(New_pos, New_rest, [New_pos|Path]):-
move(Current_pos, New_pos),
member(New_pos, Rest),
delete(New_pos,Rest,New_rest).
Γιάννης Ρεφανίδης
86
Αναζήτηση πρώτα κατά πλάτος
(5/5)

Για πιο εύκολη κλήση της bfs_tour/2 ορίζουµε τη βοηθητική διαδικασία
run_bfs/1 ως εξής:

run_bfs(Path):-
gen_table(Table),
delete(pos(1,1),Table,Rest),
assert(agenda([ state( pos(1,1), Rest, [pos(1,1)] ) ] )),
bfs_tour(Path1),
reverse(Path1,Path).

Ο αλγόριθµος αναζήτησης πρώτα κατά πλάτος δεν µπορεί να λύσει
το συγκεκριµένο πρόβληµα. Ο λόγος είναι ότι για να βρει µια λύση, η
οποία έχει µήκος 25, θα πρέπει πρώτα να εξετάσει όλες τις
διαδροµές µε µήκος 24, οι οποίες είναι πάρα πολλές (της τάξης του
8
24
).
44
Γιάννης Ρεφανίδης
87
Παρατήρηση

Θα µπορούσαµε πολύ εύκολα να µετατρέψουµε τον αλγόριθµο
αναζήτησης πρώτα κατά πλάτος σε πρώτα κατά βάθος µε δικό µας
µέτωπο αναζήτησης.

Αυτό µπορεί να γίνει αν η κλήση:

append(Rest_agenda, Children, New_agenda)

στον δεύτερο κανόνα της διαδικασίας bfs_tour/1 άλλαζε σε:

append(Children, Rest_agenda, New_agenda)
Γιάννης Ρεφανίδης
88
Αναζήτηση πρώτα στο καλύτερο
(1/5)

Η αναζήτηση πρώτα στο καλύτερο απαιτεί µια µικρή τροποποίηση
στο πρόγραµµα της αναζήτησης πρώτα κατά πλάτος:

Κάθε κατάσταση πρέπει να συνοδεύεται από έναν αριθµό, που να
δηλώνει το πόσο «καλή» είναι αυτή η κατάσταση.

Οι καταστάσεις στο µέτωπο αναζήτησης (agenda) πρέπει να είναι
ταξινοµηµένες.

Αλλάζουµε λοιπόν τον ορισµό των καταστάσεων ως εξής:

state(Score, Current_pos, Rest, Path)

όπου

Score είναι ο βαθµός κάθε κατάστασης
45
Γιάννης Ρεφανίδης
89
Αναζήτηση πρώτα στο καλύτερο
(2/5)

Χρειαζόµαστε και ένα µηχανισµό βαθµολόγησης των καταστάσεων.

Η βασική ιδέα είναι η εξής:

Προτιµούνται οι καταστάσεις για τις οποίες:

Το τρέχον τετράγωνο είναι στην άκρη της σκακιέρας.

Έχουν ήδη διανύσει µεγαλύτερα µονοπάτια.

Η παρακάτω διαδικασία βαθµολογεί καταστάσεις:

heuristic( state(Score, pos(X,Y), Rest, Path) ):-
size(S),
DistX is min(X-1, S-X),
DistY is min(Y-1, S-Y),
length(Rest, LR),
Score is DistX*DistY+4*LR.
Γιάννης Ρεφανίδης
90
Αναζήτηση πρώτα στο καλύτερο
(3/5)

Οι νέες καταστάσεις θα πρέπει να βαθµολογούνται µόλις
δηµιουργούνται.

Η διαδικασία next_state τροποποιείται ως εξής:

next_state( state(_, Current_pos, Rest, Path),
state(Score, New_pos, New_rest, [New_pos|Path]):-
move(Current_pos, New_pos),
member(New_pos, Rest),
delete(New_pos,Rest,New_rest)
heuristic(state(Score, New_pos, New_rest,_)).

Η κλήση στον κανόνα heuristic γίνεται µε τη νέα κατάσταση να έχει
δεσµευµένες όλες τις παραµέτρους της εκτός από την Score.

Ο κανόνας heuristic πετυχαίνει δεσµεύοντας την παράµετρο Score
στην ευρετική τιµή της κατάστασης.
46
Γιάννης Ρεφανίδης
91
Αναζήτηση πρώτα στο καλύτερο
(4/5)

Ο αλγόριθµος πρώτα στο καλύτερο θα πρέπει να διατηρεί το µέτωπο
αναζήτησης ταξινοµηµένο.

bestfs_tour(Path):-
retract( agenda( [ state( _, Current_pos, [ ] , Path ) | _ ] )),
!.

bestfs_tour(Path):-
retract( agenda( [ State | Rest_agenda ] )),
find_children(State, Children),
append(Rest_agenda, Children, New_agenda),
sort(New_agenda, Sorted_agenda),
assert( agenda(Sorted_agenda) ),
bestfs_tour(Path).

Η ενσωµατωµένη διαδικασία sort/2 της Prolog ταξινοµεί µια λίστα
αλφαβητικά. Στην προκειµένη περίπτωση, όλοι οι όροι της λίστας είναι
τύπου state/4, και το πρώτο σηµείο στο οποίο διαφέρουν είναι η
βαβµολογία των καταστάσεων.
Γιάννης Ρεφανίδης
92
Αναζήτηση πρώτα στο καλύτερο
(5/5)

Τέλος, για πιο εύκολη κλήση της bestfs_tour/2 ορίζουµε τη βοηθητική
διαδικασία run_bestfs/1 ως εξής:

run_bestfs(Path):-
gen_table(Table),
delete(pos(1,1),Table,Rest),
heuristic(state(Score, pos(1,1), Rest, _)),
assert(agenda([state( Score, pos(1,1), Rest, [pos(1,1)] )] )),
bfs_tour(Path1),
reverse(Path1,Path).
47
Γιάννης Ρεφανίδης
93
Παρατηρήσεις

Ο αλγόριθµος αναζήτησης πρώτα στο καλύτερο βρίσκει τη λύση σε
σηµαντικά µικρότερο αριθµό επαναλήψεων.

Συγκεκριµένα, ο αλγόριθµος πρώτα στο καλύτερο βρήκε λύση µετά
από 32 επαναλήψεις, ενώ ο αλγόριθµος πρώτα κατά βάθος µετά
από 44157.
Γιάννης Ρεφανίδης
94
Γνωστές υλοποιήσεις της
γλώσσας Prolog

Eclipse

Imperial College, London

Prolog+Constraints

http://www-
icparc.doc.ic.ac.uk/eclipse/

Ελεύθερη διανοµή για
πανεπιστήµια/φοιτητές

Sicstus Prolog

Prolog+Constraints

http://www.sics.se/sicstus/

Trial version

SWI

Απλή Prolog

http://www.swi-prolog.org/

Ελεύθερη διανοµή

LPA Prolog

Απλή Prolog

http://www.lpa.co.uk

Εµπορική