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