Prolog – bliższe spojrzenie Michał Wujkowski Przemysław Cecelski Kamil Rychliński Mateusz Iwański Listy Listy w Prologu mogą przechowywać dane dowolnego typu [alpha,beta,gamma,delta] [1,2,3] Sama lista również może zawierać listę: [[a,list,within],a,list] Listy mogą być tworzone przez zjednoczenie , cała list zostaje przypisane do danej zmiennej lub kilku zmiennych, dotyczy to również list lub struktur w nich osadzonych(przykład 3,4) np.: Unify With Result [a,b,c] X X=[a,b,c] [X,Y,Z] [a,b,c] [[a,b],c] [X,Y] [a(b),c(X)] [Z,c(a)] X=a, Y=b, Z=c X=[a,b] , Y=c X=a, Z=a(b) Każda lista może być podzielona na głowę i ogon za pomocą symbolu „ | „ . Oczywiście głowa to pierwszy element, ogon pozostałe. Ogon w odróżnieniu od głowy może być pusty. [a|[b,c,d]] = [a,b,c,d] [a|[]] = [a] [ ] - oznacza listę pustą Unify With Result [X|Y] [a,b,c,d] X=a, Y=[b,c,d] [X|Y] [a] X=a, Y=[] Najciekawsze przykłady z wieloma elementami w head/tail , nie zawsze jest to jeden element! np.:[a,b,c|[d,e,f]] = [a,b,c,d,e,f] Unify With Result [X,Y|Z] [a,b,c] X=a, Y=b, Z=[c] [X,Y|Z] [a,b,c,d] [X,Y,Z|A] [a,b,c] [X,Y,Z|A] [a,b] X=a, Y=b, Z=[c,d] X=a, Y=b, Z=c, A=[] fails Przykład Program: third_element([A,B,C|Rest],C). – definicja listy Wywołanie: 1) third_element([a,b,c,d,e,f],X). 2) third_element([a,b,Y,d,e,f],c). Wynik: 1) X = c 2) Y = c Błąd podczas kompilacji: Singleton variables: [A,B,Rest] Powyższy błąd jest spowodowany tym, iż mamy program w postaci: third_element([A,B,C|Rest],C). Gdzie elementy A,B,Rest nie są nigdzie używane i nie posiadają wartości, jeżeli takich zmiennych nie potrzebujemy możemy zastąpić je znakiem _ wtedy wspomniany błąd znika tj.: third_element([_,_,C|_],C). Zadanie, sprawdzić czy pierwsze dwa elementy są jednakowe: Program: takie_same([A,A,_|_]). Wywałania: 1) takie_same([a,b,c,d,e,f]). 2) takie_same([a,a,c,d,e,f]). 3) takie_same([a,X,c,d,e,f]). Wyniki: 1) false 2) true 3) X=a W 3. przypadku do zmiennej X przypisana została wartość pierwszego elementu Zadanie, zamienić pierwsze dwa elementy listy i zwrócić pozostałe Program takie_same([A,B|Z],[B,A|Z]). Wywołanie 1) takie_same([x,i,c,d],K) 2) takie_same([x,[1,2,3],c,d],K). Wy n i k 1 ) K = [i, x, c, d] 2 ) K = [[1, 2, 3], x, c, d] Listy jako rekordy Lista może przechowywać dane w postaci rekordów, dla przykładu przechowajmy dane osobowe: ['Michael Covington', '285 Saint George Drive', 'Athens', 'Georgia', '30606'] Najważniejszą różnicą jest fakt, że liczba elementów listy nie musi być z góry określona a elementy list nie muszą być danego typu co daje możliwość zapisanie np. daty urodzenie i jednocześnie nazwiska. Lista może zawierać inną listę jak wcześniej wspomnieliśmy dla przykładu nazwisko osoby, dzieci, szkoła: [ `Adam Nowak `, [`Marta`, `Maciek`, `Ada`], `Uniwersytet Łódzki`] Listy można użyć również jako tablicy np. do przechowania macierzy: [[1,2,3], [4,5,6], [7,8,9]] Należy pamiętać jednak, że czas dostępu do elementu na liście jest zależny od odległości elementu od początku listy, ponieważ komputer musi przejrzeć całą listę od początku aby znaleźć potrzebny nam element w przypadku tablicy czas dostępu do danej jest zawsze taki sam. Rekurencja a listy Nie zawsze możemy jasno określić ile elementów będzie miała nasza lista wtedy z pomocą przychodzi nam rekurencja. Weźmy pod uwagę takie zadanie, mamy sprawdzić czy X jest elementem listy Y, oczywiście nie wiemy z góry ile elementów ma lista Y, nie możemy w tym przypadku użyć skończonej liczby predeterminowanych pozycji, musimy przeszukiwać tak długo listę, aż nie znajdziemy elementu X lub nie skończą się dane do przeszukiwania – lista Y. Należy również obsłużyć przypadek w którym list Y będzie pusta. Program: member(X,[X|_]) member(X,[_|Ytail]) :- member(X,Ytail) Wywołanie: 1) member(c,[a,b,c]). - true 2) member(f,[a,b,c]). – false Schemat nie pasuje do klauzuli 1 więc program kontynuuje działanie w drugim „przebiegu” wywołane jest member(c,[b,c]). Idąc dalej otrzymamy member(c,[c]) w tym przypadku [c]=[c|[]] tak więc otrzymamy wartość true. Przeliczanie elementów listy Zakładamy że lista jest pusta List_length([],0). W innym przypadku pomiń pierwszy element i policz liczbę pozostałych w Tail, algorytm działa również rekurencyjnie, ponieważ żeby policzyć całość początkowo liczy elementy mniejszej listy. Kod: list_length([],0) list_length([_|Tail],K) :list_length(Tail,J), K is J + 1. ?- list_length([a,b,c],K0). ?- list_length([b,c],K1). ?- list_length([c],K2). ?-listl_ength([],0). ?- K2 is 0+1. ?- K1 is 1+1. ?- K0 is 2+1. Łączenie list Do łączenie list nie możemy użyć znaku „|” , wynikiem nadal byłaby lista w liście dla przykładu weźmy [a,b,c] z [d,e,f], aby uzyskać [a,b,c,d,e,f] W momencie kiedy użyjemy „|” otrzymamy następujący wynik: [[a,b,c],d,e,f] Na początek zajmijmy się warunkiem ograniczającym, gdyż lista ewentualnie zostanie pusta. append([],X,X). Rekurencyjna klauzula : append([X1|X2],Y,[X1|Z]) :- append(X2,Y,Z). Weź pierwszy element pierwszej listy(nazwij X1) Rekurencyjnie dołącz ogon pierwszej listy do całej drugiej listy. Nazwij rezultat Z. Dodaj X1 do początku Z. Rekurencyjne odwracanie listy Podziel oryginalną listę na head(głowę) i tail(ogon). 2. Rekurencyjnie odwrócić tail(ogon) oryginalnej listy. 3. Stworzyć liste której jedyne elementy są head(głową) oryginalnej listy. 4. Powiązać odwrócony tail(ogon) oryginalnej listy z listą stworzoną w kroku 3. 1. reverse([],[]). reverse([Head|Tail], Result) :reverse(Tail,ReversedTail), append(ReversedTail,[Head],Result). Ciągi znaków W Prologu można reprezentować ciagi znaków na 3 sposoby: - Jako atom. Atomy są kompaktowe ale ciężko nimi manipulować - Jako lista kodów ASCII - Jako lista jednoznakowych atomów. Wpisując słowo „abc”, Prolog interpretuje to jako następujące kody ASCII [97,98,99]. Teoria z książki mówi, że problemem jest wyświetlenia samych znaków ponieważ przy użycia funkcji write lub display otrzymamy liste numerów ASCII zamiast napisu. Nie zauważyliśmy tego, ponieważ kompilator w naszej wersji zwraca właściwy już napis. Zakładając jednak, że jest inaczej należy użyć listy w następujący sposób: write_str([Head|Tail]) :- put(Head), write_str(Tail). write_str([]). -- instrukcja put zamienia kod ASCII na literę Rekurencja jest prosta do śledzenia. Jeżeli string nie jest pusty (tak więc będzie się zgadzał [Head|Tail]) wypisze pierwszą pozycjie i powtórz procedure dla pozostałych pozycji. Kiedy String stanie się pusty, zakończ powodzeniem bez kolejnych akcji. Stringi są listami w każdym sensie słowa i kazda lista technik przetwarzania może być na nich użyta. Tak więc reverse odwróci string-a, append zwiąże lub podzieli string itd.. Struktury danych i obliczenia Arytmetyka Przykłady jak wygląda prosta arytmetyka w Prologu: ?- Y is 2+2. Y = 4 ?- 5 is 3+3. false ?- Z is 4.5 + (3.9 / 2.1). Z = 6.3571428 Predykat „is” oblicza wyrażenie arytmetyczne z prawej strony i przypisuje je do wyrażenia po lewej stronie. Operatory + * / // mod dodawanie odejmowanie mnożenie dzielenie zmiennoprzecinkowe dzielenie całkowite modulo Funkcje - wartość bezwzględna sqrt() – pierwiastek kwadratowy log() – logarytm, przy podstawie e exp() - funkcja wykładnicza, podstawa e floor() – największa wartość integer mniejsza lub równa argumentowi round() - zaokrąglenie do najbliższej liczby abs() Operatory R is wyrażenie – ocenia i przypisuje wartość wyrażenie1 =:= wyrażenie2 – jeżeli równe wyrażenie1 =\= wyrażenie2 – jeżeli różne wyrażenie1 > wyrażenie2 – jeżeli większe wyrażenie1 < wyrażenie2 – jeżeli mniejsze wyrażenie1 >= wyrażenie2 – jeżeli większe bądź równe wyrażenie1 =< wyrażenie2 – jeżeli mniejsze bądźrówne Konstruowanie wyrażeń ?- 4+1 =:= 2+3. true. ?- 4 is sqrt(16). false. ?- 4.0 is sqrt(16). true. ?- What is 2 + 3*4 + 5. What = 19. Ciągi znaków Są trzy sposoby na zaprezentowanie ciągu znaków w Prologu: Jako atom. Jako lista kodów ASCII. Jako lista jedno znakowych atomów. Jeśli wpiszemy („like this”) to komputer zinterpretuje to jako liste kodów ASCII. A więc „abc” i [97,98,99] dla Prologa to dokładnie to samo. Lista kodów ASCII to tradycyjnie nazywany string. Struktury Wiele warunków w Prologu składa się z funktora wynikającego z zera albo większej ilości warunków takich jak: a(b,c) alpha([beta,gamma],X) 'this and'(that) f(g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v) i_have_no_arguments Warunki tej formy nazywane są Structures(Struktury). Funktor zawsze jest atomem, ale argumenty mogą być warunkami każdego typu. Struktura bez argumentów jest po prostu atomem. Przykład użycia struktur: person(name('Michael Covington'), gender(male), birthplace(city('Valdosta'), state('Georgia'))) sentence(noun_phrase(determiner(the), noun(cat)), verb_phrase(verb(chased), noun_phrase(determiner(the) ), noun(dog)))) Struktury działają podobnie jak listy, ale inaczej są przechowywane w pamięci. Jedną z ważniejszych różnic to to, że lista jest podzielona na head i tail, a struktura nie jest. Struktura zjednoczy się z inna kiedy ma taki sam funktor i taka samą ilość argumentów. Unify With Result a(b,c) X X=a(b,c) a(b,c) a(X,Y) X=b,Y=c a(b,c) a(X) fails a(b,c) a(X,Y,Z) fails „Occurs check” Możemy stworzyć dziwaczna, zapętloną strukturę. Struktury te zawierają wskaźniki do siebie, a one doprowadzają do pętli bez końca. ?- X = f(X). X = f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f (f(f... ?- X = [a,b,X] X = [a,b,[a,b,[a,b,[a,b,[a,b,[a,b,[a,b,[a,b[a,b,[a,b[a,b, [a,b... ?- f(X) = f(f(X)) X = f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f (f(f(... Standard ISO posiada funkcje unify_with_occurs_check która odpowiada za kontrole przed próba wykonania: ?- unify_with_occurs_check(X,f(X)). no. ?- unify_with_occurs_check(X,f(a)). X = f(a) Strategie przechowywania danych Są trzy sposoby na przechowywanie danych w programach w Prologu: W instancji zmiennej. Nie jest to trwały sposób przechowywania danych, ponieważ działa tylko w zasięgu klauzuli która je definiuje. W argumentach. Lista argumentów jest sposobem by procedura komunikowała się. Procedura może wykonać rekurencyjnie proces i zapisywać informacje z jednej rekurencji do drugiej. W bazie wiedzy. Jest to najtrwalszy sposób przechowywania danych. Zadanie Napisz funkcję w prologu, która odwraca listę list, Tzn: reverseLL([[1,2,3],[4,5],[8,9]],X) X = [[3,2,1],[5,4],[9,8]] reverseLL([],[]) reverseLL([X|Y],Z) :- reverse(X,A), reverseLL(Y,B), append(A,B,Z).