Wprowadzenie

Transkrypt

Wprowadzenie
Wprowadzenie do Sztucznej Inteligencji
Laboratorium lista 0.4
Elementy języka Prolog: metaprogramowanie i
struktury danych
Przemysław Kobylański
Część I
Wprowadzenie
1
Metapredykaty
Argumentem wywołania predykatu może być warunek do sprawdzenia. Na wcześniejszych listach zadań korzystaliśmy między innymi z predykatu \+ Warunek,
który jest metapredykatem wyrażającym negację.
1.1
Przykład
Predykat forall/2 jest metapredykatem. Jego argumentami są dwa warunki:
• generator – warunek dostarczający kolejne wartości,
• walidator – warunek sprawdzający wygenerowane wartości.
Predykat forall(Generator, Walidator) jest spełniony gdy wszystkie wartości wygenerowane przez Generator spełniają warunek Walidator.
Krótki dialog:
?- forall(member(X, [2, 4, 6, 8]), 0 =:= X mod 2).
true.
?- forall(member(X, [2, 4, 5, 8]), 0 =:= X mod 2).
false.
2
Kolekcjonowanie wyników
Kolejne wyniki (odpowiedzi na zadane pytanie) można oglądać naciskając średnik
po wydrukowaniu przez system Prolog każdego z nich. Jeśli odpowiedzi jest wiele,
można stosując drukowanie i nawroty wyświetlić je wszystkie bez potrzeby żmudnego naciskania średnika:
?- append(X, Y, [1, 2, 3, 4, 5]), write(X - Y), nl, fail.
[]-[1,2,3,4,5]
[1]-[2,3,4,5]
[1,2]-[3,4,5]
1
[1,2,3]-[4,5]
[1,2,3,4]-[5]
[1,2,3,4,5]-[]
false.
Często istnieje jednak konieczność zebrania wszystkich wyników na liście celem
ich dalszego przetwarzania, a nie tylko wydrukowania ich na ekranie.
Niech T będzie termem zawierającym zmienne a G warunkiem generującym wartości dla tych zmiennych.
Predykat findall(T, G, L) tworzy listę wszystkich przykładów termu T powstałych przez podstawianie za zmienne z termu T wartości wygenerowanych warunkiem G.
Na liście L mogą pojawić się wielokrotnie te same termy. Jeśli chcemy kolekcjonować termy bez powtórzeń (utworzyć ich zbiór a nie listę), należy użyć predykatu
setof(T, G, L).
Lista utworzona przez predykat setof/3 nie tylko nie zawiera powtórzeń ale
również jest uporządkowana zgodnie z liniowym porządkiem na termach @<.
Przy predykacie setof/3 trzeba uważać na zmienne, które występują w warunku
G ale nie występują w termie T. Aby faktycznie zebrać wszystkie przykłady termu
należy zastosować kwantyfikator egzystencjalny. Zapis X^G, gdzie X jest zmienną występującą w warunku G ale nie w termie T, należy rozumieć jako (∃X)G.
Warunek findall/3 odpowiada warunkowi bagof/3, przy czym zachodzą następujące dwie różnice:
1. Jeśli lista przykładów jest pusta, to findall/3 nie zawodzi, natomiast bagof/3
zawodzi.
2. bagof/3 umożliwia korzystania z kwantyfikatora egzystencjalnego a findall/3
nie pozwala na to.
Więcej o znajdowaniu wszystkich rozwiązań w rozdziale podręcznika: Finding
all Solutions to a Goal.
2.1
Przykłady
p(b).
p(a).
p(b).
p(a).
?- findall(X, p(X), L).
L = [b, a, b, a].
?- setof(X, p(X), L).
L = [a, b].
?- findall(K, (between(-2, 2, I), K is I*I), L).
L = [4, 1, 0, 1, 4].
?- setof(K, (between(-2, 2, I), K is I*I), L).
I = -2,
L = [4] ;
I = -1,
L = [1] ;
I = 0,
2
L
I
L
I
L
=
=
=
=
=
[0] ;
1,
[1] ;
2,
[4].
?- setof(K, I^(between(-2, 2, I), K is I*I), L).
L = [0, 1, 4].
?- findall(X, (member(X, [1, 2, 3]), X > 4), L).
L = [].
?- bagof(X, (member(X, [1, 2, 3]), X > 4), L).
false.
?- bagof(X, (member(X, [1, 2, 3]), X > 2), L).
L = [3].
3
Metaprogramowanie
Wykorzystywanie metapredykatów bardzo upraszcza zapis programów gdyż dostarcza ogromną moc języka.
Możliwe jest w nim budowanie termu, który stanie się warunkiem do udowodnienia:
?- Warunek =.. [append, X, Y, [1, 2, 3]], Warunek.
Warunek = append([], [1, 2, 3], [1, 2, 3]),
X = [],
Y = [1, 2, 3] ;
Warunek = append([1], [2, 3], [1, 2, 3]),
X = [1],
Y = [2, 3] ;
Warunek = append([1, 2], [3], [1, 2, 3]),
X = [1, 2],
Y = [3] ;
Warunek = append([1, 2, 3], [], [1, 2, 3]),
X = [1, 2, 3],
Y = [] ;
false.
W powyższym przykładzie użyto predykat =../2, który służy do konstruowania
i rozkładania termów na listę złożoną z funktora i jego argumentów:
?- a =.. X.
X = [a].
?- f(a) =.. X.
X = [f, a].
?- f(a, g(b)) =.. X.
X = [f, a, g(b)].
?- X =.. [f, a, g(b), c].
X = f(a, g(b), c).
3
3.1
3.1.1
Przykłady
Łamigłówka
Każdy mieszkaniec wyspy jest albo rycerzem (zawsze mówi prawdę) albo łotrem
(zawsze kłamie). Pewnego dnia B stwierdził: "A powiedział o sobie, że jest łotrem".
Kim jest B?
rycerz(rycerz).
lotr(lotr).
powiedzial(rycerz, X) :- X.
powiedzial(lotr, X) :- \+ X.
?- powiedzial(B, powiedzial(A, lotr(A))).
B = lotr.
3.1.2
Interpreter Prologu w Prologu
Predykat clause/2 umożliwia pobieranie klauzul składających się na program w
Prologu. Warunek clause(Head, Body) jest spełniony gdy dla danej formuły atomowej Head istnieje klauzula o głowie unifikowalnej z Head i ciele (treści) unifikowalneym z Body. W wyniku nawrotów podstawiane będą kolejne klauzule o głowach
pasujących do Head.
app([], X, X).
app([X | Y1], Y2, [X | Z]) :app(Y1, Y2, Z).
interpreter(true).
interpreter((G1, G2)) :interpreter(G1),
interpreter(G2).
interpreter(A) :A \= true,
A \= (_, _),
clause(A, B),
interpreter(B).
[debug] ?- interpreter(app(X, Y, [1, 2, 3])).
X = [],
Y = [1, 2, 3] ;
X = [1],
Y = [2, 3] ;
X = [1, 2],
Y = [3] ;
X = [1, 2, 3],
Y = [] ;
false.
Powyższy interpreter obsługuje tylko predykaty zdefiniowane klauzulami, których ciała są warunkiem true (gdy fakt) albo jedną lub koniunkcją formuł atomowych połączonych przecinkami (gdy reguła).
4
4
Struktury danych
W Prologu dostępne są moduły definiujące między innymi następujące struktury
danych:
• zbiory uporządkowane
• kopce (kolejki priorytetowe)
• drzewa czerwono-czarne
4.1
Zbiory uporządkowane
Zbiory można reprezentować w Prologu w postaci list uporządkowanych bez powtarzających się elementów.
Aby skorzystać z dostępnych w Prologu predykatów na zbiorach uporządkowanych, należy zaimportować moduł ordsets w pliku źródłowym dyrektywą:
:- use_module(library(ordsets)).
albo zadając systemowi sc Prolog pytanie:
?- use_module(library(ordsets)).
Przykład dialogu o zbiorach uporządkowanych:
?- list_to_ord_set([1, 2, 3, 2, 3, 4], X), ord_memberchk(2, X).
X = [1, 2, 3, 4].
?- list_to_ord_set([1, 2, 3, 2, 3, 4], X), ord_memberchk(5, X).
false.
?- list_to_ord_set([1, 2, 1], X1), list_to_ord_set([2, 1, 3], X2),
ord_intersection(X1, X2, X).
X1 = X, X = [1, 2],
X2 = [1, 2, 3].
?- list_to_ord_set([1, 2, 1], X1), list_to_ord_set([2, 1, 3], X2),
ord_union(X1, X2, X).
X1 = [1, 2],
X2 = X, X = [1, 2, 3].
?- list_to_ord_set([1, 2, 1], X1), list_to_ord_set([2, 1, 3], X2),
ord_symdiff(X1, X2, X).
X1 = [1, 2],
X2 = [1, 2, 3],
X = [3].
Więcej o zbiorach uporządkowanych w dodatku podręcznika: library(ordsets):
Ordered set manipulationl.
4.2
Drzewa czerwono-czarne
Aby skorzystać z dostępnych w Prologu predykatów na drzewach czerwono-czarnych,
należy zaimportować moduł rbtrees w pliku źródłowym dyrektywą:
:- use_module(library(rbtrees)).
5
albo zadając systemowi sc Prolog pytanie:
?- use_module(library(rbtrees)).
Elementy wstawiane są do drzewa czerwono-czarnego wraz z kluczami względem
których drzewo jest uporządkowane. Najprościej stworzyć drzewo czerwono-czarne
tak jak w poniższym przykładzie, tj. przez zamianę listy zawierającej pary Klucz
- Element:
?- list_to_rbtree([2-a, 0-b, 5-c, 1-d], RB0),
rb_del_min(RB0, MinKlucz, MinElement, RB1),
rb_del_max(RB1, MaxKlucz, MaxElement, RB2).
RB0 = t(black(’’, _G1908, _G1909, ’’), black(black(black(’’, _G1908,
_G1909, ’’), 0, b, black(’’, _G1908, _G1909, ’’)), 1, d,
black(black(’’, _G1908, _G1909, ’’), 2, a, red(black(’’, _G1908,
_G1909, ’’), 5, c, black(’’, _G1908, _G1909, ’’))))),
MinKlucz = 0,
MinElement = b,
RB1 = t(black(’’, _G1908, _G1909, ’’), black(black(black(’’, _G1908,
_G1909, ’’), 1, d, black(’’, _G1908, _G1909, ’’)), 2, a,
black(black(’’, _G1908, _G1909, ’’), 5, c, black(’’, _G1908,
_G1909, ’’)))),
MaxKlucz = 5,
MaxElement = c,
RB2 = t(black(’’, _G1908, _G1909, ’’), black(red(black(’’, _G1908,
_G1909, ’’), 1, d, black(’’, _G1908, _G1909, ’’)), 2, a,
black(’’, _G1908, _G1909, ’’))).
W powyższym przykładzie:
RB0 jest drzewem czerwono-czarnym zawierającym pary 2-a, 0-b, 5-c, 1-d,
RB1 jest drzewem czerwono-czarnym powstałym z RB0 przez usunięcie elementu o
najmniejszym kluczu,
RB2 jest drzewem czerwono-czarnym powstałym z RB1 przez usunięcie elementu o
największym kluczu.
Więcej o drzewach czerwono-czarnych w opisie biblioteki: rbtrees.pl – Red
black trees.
4.3
Kopce/kolejki priorytetowe
Celem szybkiego dostępu do minimalnego elementu i usuwania go ze zbioru, można
użyć strukturę kopca (kolejki priorytetowej).
Aby skorzystać z dostępnych w Prologu predykatów na kopcach (kolejkach
priorytetowych), należy zaimportować moduł heaps w pliku źródłowym dyrektywą:
:- use_module(library(heaps)).
albo zadając systemowi sc Prolog pytanie:
?- use_module(library(heaps)).
Elementy (klucze) w kopcu (kolejce priorytetowej) przechowywane są w postaci
par Priorytet - Klucz, gdzie Priorytet jest wartością względem której Klucz
jest uporządkowany w kopcu (kolejce priorytetowej).
W poniższym przykładzie wstawiono trzy klucze e1, e2, e3 o priorytetach,
odpowiednio, 20, 10, 30 a następnie usunięto z utworzonego kopca (kolejki priorytetowej) Klucz o najmniejszym priorytecie:
6
?- list_to_heap([], H0), add_to_heap(H0, 20, e1, H1),
add_to_heap(H1, 10, e2, H2), add_to_heap(H2, 30, e3, H3),
get_from_heap(H3, Priorytet, Klucz, H4).
H0 = heap(nil, 0),
H1 = heap(t(e1, 20, []), 1),
H2 = heap(t(e2, 10, [t(e1, 20, [])]), 2),
H3 = heap(t(e2, 10, [t(e3, 30, []), t(e1, 20, [])]), 3),
Priorytet = 10,
Klucz = e2,
H4 = heap(t(e1, 20, [t(e3, 30, [])]), 2).
W powyższym przykładzie:
H0 jest początkowo pustym kopcem,
H1 jest kopcem zawierającym jedynie klucz e1,
H2 jest kopcem zawierającym klucze e1 i e2,
H3 jest kopcem zawierającym klucze e1, e2 i e3,
H4 jest kopcem powstałym z H3 po usunięciu klucza o najmniejszym priorytecie.
Więcej o kopcach/kolejkach priorytetowych w opisie biblioteki: heaps.pl – heaps/priorit queues.
Część II
Zadania i polecenia
Polecenie 1
Załóżmy, że dana jest baza o zarobkach pracowników w postaci następujących faktów:
pracownik(abacki, 3700).
pracownik(babacki, 5200).
pracownik(cabacki, 2500).
Skompiluj powyższe fakty (np. umieszczając je w pliku baza.pl i kompilując
ją).
Jeśli chcemy poznać nazwiska pracowników w kolejności rosnących zarobków,
możemy zrobić to następującym pytaniem:
?- setof(Zarobek - Pracownik, pracownik(Pracownik, Zarobek), Zbior),
member(_ - Nazwisko, Zbior).
Zbior = [2500-cabacki, 3700-abacki, 5200-babacki],
Nazwisko = cabacki ;
Zbior = [2500-cabacki, 3700-abacki, 5200-babacki],
Nazwisko = abacki ;
Zbior = [2500-cabacki, 3700-abacki, 5200-babacki],
Nazwisko = babacki.
7
W powyższym przykładzie lista Zbior jest uporządkowaną listą par Zarobek Pracownik. Pary takie uporządkowane są według następującego porządku liniowego:
Z1 − P1 < Z2 − P2
≡ (Z1 < Z2 ) ∨
(Z1 = Z2 ∧ P1 @<P2 ),
gdzie @</2 jest liniowym porządkiem na termach.
Zadanie 2
Elementy macierzy:

5
A= 7
10

3
4
3
2
0 −2 
−1 −4
2
zapisano w postaci faktów tab(i, j, aij ):
tab(1, 1, 5). tab(1, 2, 3). tab(1, 3, 4). tab(1, 4, 3).
tab(2, 1, 7). tab(2, 2, 2). tab(2, 3, 0). tab(2, 4, -2).
tab(3, 1, 10). tab(3, 2, -1). tab(3, 3, -4). tab(3, 4, 2).
Niech warunek siodlo(I, J) wyraża, to że element aij tak zadanej macierzy
jest punktem siodłowym, tj. zachodzi:
(∀k )(∀l ) akj ≤ aij ≤ ail
Napisz predykat siodlo(I, J).
1. Czy potrafisz tak napisać predykat siodlo(I, J) aby znajdował wszystkie
punkty siodłowe przykładowej macierzy A w mniej niż 100 wnioskowań (użyj
predykat time/1 do pomiaru).
2. Wskazówka: użyj negację.
Przykład pomiaru liczby wnioskowań przy wyznaczaniu wszystkich punktów
siodłowych:
?- time((siodlo(_, _), fail)).
% 63 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 1702703 Lips)
false.
Zadanie 3
Załóżmy, że formuły iap(E1, N, E2) wyrażają fakt, że element E1 jest częścią (isa-part) elementu E2 i występuje w nim w N egzemplarzach (np. iap(kolo, 2,
rower).
Poniżej przedstawiono kilka faktów opisujących obiekt o1 złożony z obiektów
o2, o3, o4, o5:
iap(o2,
iap(o3,
iap(o4,
iap(o5,
2,
3,
2,
5,
o1).
o1).
o3).
o3).
Powyższe fakty można zrozumieć następująco:
1. Aby zmontować jedną sztukę o1 należy połączyć ze sobą dwie sztuki o2 i trzy
sztuki o3.
8
2. Aby zmontować jedną sztukę o3 należy połączyć ze sobą dwie sztuki o4 i
pięć sztuk o5.
3. Elementy o2, o4 i o5 nie są montowane zatem należy je zakupić.
Napisz predykat zamowienie(Produkt, N, Lista), który planuje wielkość zakupów na potrzeby wyprodukowania N egzemplarzy Produktu. Zamówienie powinno
być w postaci listy o elementach postaci Liczba * Element, wyrażających potrzebę zamówienia danej liczby elementów.
Przykładowy dialog:
?- zamowienie(o1, 10, X).
X = [20*o2, 60*o4, 150*o5].
?- zamowienie(o3, 50, X).
X = [100*o4, 250*o5].
9

Podobne dokumenty