Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Struktury danych i obliczenia w Prologu Autorzy: Kamil Krajewski Mateusz Szymański Paweł Bińkowski.

Podobne prezentacje


Prezentacja na temat: "Struktury danych i obliczenia w Prologu Autorzy: Kamil Krajewski Mateusz Szymański Paweł Bińkowski."— Zapis prezentacji:

1 Struktury danych i obliczenia w Prologu Autorzy: Kamil Krajewski Mateusz Szymański Paweł Bińkowski

2 Arytmetyka Przykłady obliczeń w Prologu: ?- Y is 2+2. Y=4 ?- 5 is 3-3. False ?- Z is 4.5 + (3.9 / 2.1). Z = 6.3571428

3 Wbudowany predykat „is” bierze wyrażenie arytmetyczne z jego prawej strony, oblicza je i unifikuje je z wyrażeniem po jego lewej stronie. Wyrażenia w Prologu są bardzo podobne do tych z innych języków programowania. Najprostsze wyrażenie składa się z samej liczby: Operatory infix: + dodawanie - odejmowanie * mnożenie / dzielenie typu floating-point // dzielenie typu integer mod modulo Funkcje: abs() wartość bezwzględna sqrt() pierwiastek kwadratowy log() logarytm exp() antylogarytm floor() największa wartość typu integer mniejsza lub równa argumentowi round() najbliższa wartość typu integer

4 Kolejność wykonywania działań jest taka sama jak w innych językach programowania i matematyce. Prolog obsługuje wartości typu floating-point orazinteger i przekształca je w razie potrzeby. Warto zauważyć, że Prolog nie potrafi rozwiązywać równań matematycznych; ?- 5 is 2 + Cos. instantiation error może być to zaskakujące, ze względu na fakt, że Prolog potrafi znaleźć niewiadomą w wyrażeniu ojciec(michal, Kto), ale nie w 5 is 2 + Cos. Po namyśle można jednak zauważyć, że pierwsze zapytanie może zostać rozwiązane poprzez podstawienie wszystkich z możliwych zależności, natomiast drugie zapytanie nie może być rozwiązane w ten sposób, ponieważ, dla „is” nie ma zdefiniowanej zależności, a próba znalezienia rozwiązania oznaczała by przeszukiwanie nieskończonej liczby potencjalnych liczb w kilku wymiarach. Jedynymi metodami rozwiązania byłaby manipulacja algebraiczna(5 – 2 = Cos) lub numeryczna(omówiona w rozdziale 7).

5 Konstruowanie wyrażeń Dużą różnicą między Prologiem, a innymi językami programowania jest to, że inne języki wyliczają wyrażenia arytmetyczne, kiedykolwiek wystąpią, zaś Prolog wylicza je tylko w konkretnych miejscach. Dla przykładu; 2+2 jest wyliczane w 4tylko wtedy gdy jest argumentem predykatu arytmetycznego, w innych przypadkach jest to tylko jakaś struktura danych składająca się z 2, + i 2:is bierze wyrażenie po prawej stronie, wylicza je i unifikuje rezultat z argumentem po lewej stronie, =:= wylicza dwa wyrażenia i porównuje wyniki, = unifikuje dwa wyrażenia, bez ich wyliczania, Zatem: ?- Cos is 2+3. Cos = 5 ?- 4+1 =:= 2+3. true ?- Cos = 2+3. Cos = 2+3

6 Inne porównania(, = =) działają tak jak =:= R is Wyr wylicza Wyr i unifikuje z R Wyr1 =:= Wyr2 - prawda jeśli wyniki obu wyrażeń są równe, Wyr1 =\= Wyr2 - prawda jeśli wyniki obu wyrażeń nie są równe, Wyr1 > Wyr2 Wyr1 < Wyr2 Wyr1 >= Wyr2 Wyr1 =< Wyr2 Uwaga: = =, nie.

7 Należy zauważyć, że predykaty porównawcze wymagają konkretnych argumentów; nie możemy powiedzieć: „Podaj mi liczbę mniejszą od 20”, ponieważ takie zapytanie miałoby nieskończoną liczbę możliwych odpowiedzi. Uwaga: Liczba zmienno-przecinkowa uzyskana w obliczeniach komputerowych prawie nigdy nie jest dokładnie równa jakiejkolwiek innej liczbie zmienno-przecinkowej, nawet jeśli obie wyglądają tak samo na wyjściu. Dzieje się tak, ponieważ komputery liczą w systemie binarnym, a my piszemy liczby w systemie dziesiętnym. Wiele dziesiętnych liczb, jak 0.1 nie ma binarnej reprezentacji ze skończoną liczbą znaków. (Wyrażanie 1/10 w notacji binarnej jest jak wyrażanie 1/3 lub 1/7 w notacji dziesiętnej – znaki po prawej stronie kropki powtarzają się w nieskończoność.) W wyniku tego liczby zmienno-przecinkowe są przedmiotem błędów zaokrągleń.

8 Praktyczne obliczenia Uważny czytelnik zauważy, że używając wyrażeń w Prologu, mieszamy dwa style kodowania. Z logicznego punktu widzenia „suma” i „produkt” to zależności między liczbami, tak jak „ojciec” i „matka” są zależnościami między ludźmi, więc zamiast ?- Cos is 2 + 3*4 + 5 powinniśmy pisać ?- produkt(3,4,P), suma(2,P,S), suma(S,5,Cos) I rzeczywiście wczesne wersje Prologa tak działały, ale te stare podejście ma dwa problemy; jest niepraktycznie i daje wrażenie, że Prolog posiada strategię wyszukiwania dla liczb, co nie jest prawdą. Używamy więc wyrażeń.

9 Jeśli chcemy implementować numeryczne algorytmy, musimy zdefiniować własne predykaty. Dla przykłady zdefiniujmy predykat close_enough/2 który zadziała dla dwóch liczb różniących się o 0.0001. To pozwoli nam porównać liczby zmienno-przecinkowe bez bycia zmylonymi przez błędy zaokrągleń. close_enough(X,X) :- !. close_enough(X,Y) :- X<Y, Y-X< 0.0001. close_enough(X,Y) :- X>Y, close_enough(Y,X). Pierwsza klauzula obsługuje przypadek gdy dwa argumenty rzeczywiście są równe, wykrzyknik zapewnia, że jeśli pierwsza klauzula się wykona, dwie następne nie będą uruchomione. Druga klauzula to serce obliczeń; porównaj X i Y, odejmij mniejszą od większej, i sprawdź czy różnica jest mniejsza od 0.0001.Trzecia klauzula zajmuje się argumentami w przeciwnej kolejności – zamienia je i wywołuje samą siebie ponownie.

10 Teraz wykonamy obliczenia, następujący predykat inicjalizuje Y wartością „realsquare root” X jeśli istnieje lub jest atomem „nonexistent” jeśli nie; real_square_root(X,nonexistent) :- X = 0.0, Y is sqrt(X). Przykład użycia: ?- real_square_root(9.0,Root). Root =3.0 ?- real_square_root(-1.0,Root). Root =nonexistent Należy zauważyć, że zapytanie real_square_root(121.0,11.0) prawdopodobnie zawiedzie, ponieważ 11.0 nie równa się dokładnie zmienno-przecinkowemu wynikowi sqrt pomimo, że pierwiastek ze 121 wynosi dokładnie 11. Możemy to naprawić wykorzystując naszpredykat close_enough. Wymaga to następującej redefinicji real_square_root

11 real_square_root(X,nonexistent) :- X < 0.0. real_square_root(X,Y) :- X >= 0.0, R is sqrt(X), close_enough(R,Y). Teraz mamy nasz pożądany rezultat: ?- real_square_root(121.0, 11.0) True Wreszcie, wykorzystajmy zdolność Prologa do zwracania alternatywnych odpowiedzi, każda dodatnia liczba rzeczywista ma dwa pierwiastki kwadratowe, jeden dodatni i jeden ujemny.

12 Potrzebujemy dodatkowych klauzul dla alternatyw, ponieważ arytmetyka w Prologu jest całkowicie deterministyczna; real_square_root(X,Y) :- X > 0.0, R is -sqrt(X), close_enough(R,Y). To daje nam alternatywną metodę znajdowania pierwiastka. Teraz każde wywołanie zakończone sukcesem zwróci dwa wyniki: ?- real_square_root(9.0,Root). Root =3.0 Root =-3.0

13 Testowanie dla inicjalizacji Jak dotąd real_square_root wymaga aby pierwszy argument był zainicjalizowany, ale z drobnymi zmianami może zostać wyposażony w wymienność niewiadomych. Używając X i Y nasza strategia wygląda tak: Jeśli X jest znane unifikuj Y z pierwiastkiem z X lub ujemnym pierwiastkiem z X(alternatywne rozwiązania). Jeśli Y jest znane, unifikuj X z Y*Y. Aby to osiągnąć musimy sprawdzić czy X i Y są zainicjalizowane. Prolog dostarcza dwa predykaty w tym celu: var który zwraca prawdę jeśli argument jest niezainicjalizowany oraz nonvar, który zwraca prawdę, jeśli argument ma wartość. Możemy zatem udoskonalić real_square_root;

14 real_square_root(X,nonexistent) :- nonvar(X), X < 0.0. real_square_root(X,Y) :- nonvar(X), X >= 0.0, R is sqrt(X), close_enough(R,Y). real_square_root(X,Y) :- nonvar(X), X > 0.0, R is -sqrt(X), close_enough(R,Y). real_square_root(X,Y) :- nonvar(Y), Ysquared is Y*Y, close_enough(Ysquared,X). Tutaj klauzula 4 zapewnia metodę wyliczenia X z Y, a użycie nonvar zapewnia, że właściwa klauzula zostanie wybrana i nie będziemypróbować liczyć lub porównywać niezainicjalizowanych zmiennych.

15 Listy Jedną z najważniejszych struktur danych Prologa jest lista. Lista to posortowanasekwencja zera lub więcej termów wypisanych w nawiasach kwadratowych,oddzielonych kropkami: [alpha,beta,gamma,delta] [1,2,3,go] [(2+2),in(austin,texas),-4.356,X] [[a,list,within],a,list] Elementy listy mogą być termami dowolnego typu, włączając w to inne listy.Pusta lista to []. Warto zauważyć, że jednoelementowa lista [a] nie jest równa atomowi a. Listy mogą być konstruowane bądź rozkładane poprzez unifikacje. Cała lista może oczywiście równać się jednej zmiennej. Unify With Result [a,b,c] X X=[a,b,c]

16 Także, odpowiadające element dwóch list mogą być unifikowane jedna do drugiej Unify With Result [X,Y,Z] [a,b,c] X=a,Y=b, Z=c [X,b,Z] [a,Y,c] X=a,Y=b, Z=c Ta zasada odnosi się także do list lub struktur zagnieżdżonych w listach Unify With Result [[a,b],c] [X,Y] X=[a,b], Y=c [a(b),c(X)] [Z,c(a)] X=a, Z=a(b) Co ważniejsze jakakolwiek lista może zostać podzielona na głowę i ogon poprzez symbol „|”. Głową listy jest pierwszy element, zaś ogonem lista pozostałych elementów(może być pusta). Każda nie pusta lista ma głowę i ogon; [a|[b,c,d]] = [a,b,c,d] [a|[]] = [a]

17 Term [X|Y] unifikuje z jakąkolwiek nie pustą listą, inicjalizując X do głowy i Y do ogona: Unify With Result [X|Y] [a,b,c,d] X=a,Y=[b,c,d] [X|Y] [a] X=a, Y=[] Jak dotąd | jest jak rozróżnienie CAR-CDR w Lispie, ale w przeciwieństwie do CAR i CDR, | może wziąć pod uwagę więcej niż jeden początkowy element w pojedynczym kroku: [a,b,c|[d,e,f]] = [a,b,c,d,e,f]

18 Co naprawdę przydaję się w unifikacji: Unify With Result [X,Y|Z] [a,b,c] X=a, Y=b, Z=[c] [X,Y|Z] [a,b,c,d] X=a, Y=b, Z=[c,d] [X,Y,Z|A] [a,b,c] X=a, Y=b, Z=c, A=[] [X,Y,Z|A] [a,b] fails [X,Y,a] [Z,b,Z] X=Z=a, Y=b [X,Y|Z] [a|W] X=a, W=[Y|Z] Proces konstrukcji i rozkładania list jest wykonywany głównie poprzez unifikację, nie przez procedury. To znaczy, że serce procedury przetwarzającej listę jest często w notacji, która opisuje strukturę argumentów. Aby się do tej notacji przyzwyczaić, zdefiniujmy przetwarzający predykat: third_element([A,B,C|Rest],C).

19 Osiąga on sukces, jeśli pierwszy argument jest listą, a drugi jest trzecim elementem listy. Posiada kompletną wymienność niewiadomych, więc: ?- third_element([a,b,c,d,e,f],X). X= c ?- third_element([a,b,Y,d,e,f],c). Y= c ?- third_element(X,a). X= [_0001,_0002,a|_0003] W ostatnim użyciu predykatu, komputer nie wie nic o X poza tym, że jest lista, której trzecim elementem jest, więc tworzy listę z niezainicjalizowanym 1 i 2 elementem, następnie a, a po nim niezainicjalizowany ogon.

20 Przechowywanie danych w listach Listy mogą przechowywać dane podobnie jak rekordy w wCOBOL-u bądź Pascalu. Dla przykładu; ['Michael Covington', '285 Saint George Drive', 'Athens', 'Georgia', '30606'] Jest to rozsądna metoda przedstawienia adresu, z polami dla imienia, ulicy, miasta, stanu oraz adresu pocztowego. Procedury takie jak third_element mogą wydobywać bądź wprowadzać dane do takiej listy.

21 Zasadniczą różnicą jest fakt, że pomiędzy listą, a rekordem danych jest fakt, że liczba elementów listy nie musi być z góry zadeklarowana. Kolejną różnicą jest to, że elementy listy nie muszą być żadnego konkretnego typu. Atomy, struktury i liczby mogą być dowolnie wykorzystywane w jakiejkolwiek kombinacji. Co więcej, lista może zawierać inną listę jako jeden ze swoich elementów. ['Michael Covington', [['B.A',1977], ['M.Phil.',1978], ['Ph.D.',1982]], 'Associate Research Scientist', 'University of Georgia'] W tym przykładzie główna lista posiada cztery elementy: imię, listę tytułów naukowych, stanowisko w pracy, pracodawca. Lista stopni naukowych posiada 3elementy z których każdy jest dwu-elementową listą zawierającą stopień i datę. Zauważmy, że liczba stopni naukowych na osobę nie jest ograniczona; ta sama struktura może obsłużyć osobę bez żadnych luz z kilkunastoma.

22 Listy w Prologu mogą posłużyć jako zamiennik dla tablic z innych języków programowania. Np. macierz liczb może być przedstawiona jako lista list: [[1,2,3], [4,5,6], [7,8,9]] Istnieje jednak ważna różnica, w tablicy każdy element ma taki sam czas dostępu, pracując na liście komputer musi zawsze zaczynać od początku i przechodzić element po elemencie.

23 Rekurencja Aby całkowicie wykorzystać potencjał list, potrzebujemy metody pracowania z elementami listy, bez potrzeby określania ich pozycji z wyprzedzeniem. Rozwiązaniem jest rekurencja, czyli metoda kiedy procedura wywołuje samą siebie. Zdefiniujmy predykat member(X,Y) który zwraca prawdę, jeśli X jest elementem tablicy Y. Nie wiemy z góry ile elementów posiada lista Y, nie możemy więc użyć skończonej liczby predeterminowanych pozycji, musimy kontynuować sprawdzanie, dopóki nie znajdziemy X lub elementy Y nie skończą się.

24 Zanim zajmiemy się samą rekurencją, pomyślmy nad dwoma specjalnymi przypadkami które nie są powtarzalne: 1) Jeśli Y jest puste, zwróć fałsz bez dalszych czynności, ponieważ nic nie jest elementem pustej listy, 2) Jeśli X jest pierwszym elementem Y zwróć prawdę bez dalszych czynności. Z pierwszą sytuacją uporamy się upewniając się, że we wszystkich naszych klauzulach, drugi argument jest czymś co nie unifikuje się z pustą listą, pusta lista nie ma ogona, więc możemy wykluczyć puste listy pozwalając drugiemu argumentowi być listą, która ma zarówno głowę jak i ogon. member(X,[X|_]). Następnie rekurencyjna część: member(X,[_|Ytail]) :- member(X,Ytail). Przykładowe uzycie: ?- member(c,[a,b,c]).

25 To nie pasuje do klauzuli 1, więc program kontynuuje do drugiej, ta z kolei generuje nowe zapytanie: ?- member(c,[b,c]). Ponownie, klauzula 1 nie pasuje, ale klauzula 2 już tak i generuje nowe zapytanie: ?- member(c,[c]). Tym razem zadziała klauzula 1, ponieważ [c] jest równoznaczne z [c|[]], zapytanie kończy się sukcesem, zgodnie z oczekiwaniami.

26 Liczenie elementów listy Oto rekurencyjny algorytm do liczenia elementów listy: 1) Jeśli lista jest pusta, to ma 0 elementów, 2) W innym wypadku, pomiń pierwszy element, policz liczbę pozostałych elementów i dodaj 1. Druga z tych klauzul jest rekurencyjna ponieważ, w celu policzenia elementów listy, musisz policzyć elementy innej, mniejszej liczby. Algorytm zapisany w Prologu ma postać: list_length([],0). list_length([_|Tail],K) :- list_length(Tail,J), K is J + 1.

27 Rekurencja kończy się ponieważ lista w końcu stanie się pusta po usuwaniu kolejnych elementów. Przykład użycia: ?- 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.

28 Łączenie list Co jeśli chcielibyśmy połączyć dwie listy? Np. [a,b,c] z [d,e,f], aby uzyskać[a,b,c,d,e,f]. „|” nie spełni zadania; [[a,b,c]|[d,e,f]] daje [[a,b,c],d,e,f], a to nie to czego oczekujemy. Będziemy musieli przejść po pierwszej liście element po elemencie, dodając je do drugiej listy. Na początek zajmijmy się warunkiem ograniczającym, gdyż lista ewentualnie zostanie pusta. append([],X,X). Rekurencyjna klauzula jest mniej intuicyjna, ale bardzo zwięzła: append([X1|X2],Y,[X1|Z]) :- append(X2,Y,Z). Pierwszy element wynikowy jest taki sam jak pierwszy elementem pierwszej listy. Ogon wyniku jest uzyskany poprzez dołączenie ogona pierwszej listy do całej drugiej listy. Bardziej proceduralnie: 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.

29 Z powodu deklaratywnej natury appena jest całkowicie wymienna dla niewiadomych ?- append([a,b,c],[d,e,f],X). X= [a,b,c,d,e,f] ?- append([a,b,c],X,[a,b,c,d,e,f]). X= [d,e,f] ?- append(X,[d,e,f],[a,b,c,d,e,f]). X= [a,b,c] Każde z tych jest deterministyczne – istnieje tylko jedno możliwe rozwiązanie, ale jeśli zostawimy dwa pierwsze argumenty niezainicjalizowane, dostaniemy, jako alternatywne rozwiązania, wszystkie możliwe sposoby podzielenia drugiej argumentu na dwie podlisty: ?- append(X,Y,[a,b,c,d]). X=[] Y=[a,b,c,d] X=[a] Y=[b,c,d] X=[a,b] Y=[c,d] X=[a,b,c] Y=[d] X=[a,b,c,d] Y=[]

30 Rekurencyjnie odwracanie list Klasyczny algorytm rekurencyjny dla odwrócenia elementów na liście: Podziel oryginalną listę na head(głowę) i tail(ogon).Rekurencyjnie odwrócić tail(ogon) oryginalnej listy. Stworzyć listę której jedyne elementy są head(głową) oryginalnej listy. Powiązać odwrócony tail(ogon) oryginalnej listy z listą stworzoną w kroku 3. Ponieważ lista staje się krótsza za każdym razem, ograniczający przypadkiem jest pusta lista, którą chcemy zwrócić nie zmienioną. W Prologu: reverse([],[]). % Klauzula 1 reverse([Head|Tail], Result) :- % Klauzula 2 reverse(Tail,ReversedTail), append(ReversedTail,[Head],Result).

31 Jest to tłumaczenie klasycznego algorytmu odwracania listy Lisp-a(rodzina języków programowania) znany jako „naive reversal” albo NREV i często używany do testowanie prędkości realizacji Lisp-a i Prolog-a. Jego naiwność polega na jego wielkiej nieskuteczności. Można pomyśleć ze 8elementowa lista możliwa jest do odwrócenia w 8-9 krokach. Z tym algorytmem jednak odwrócenie 8 elementowej listy potrzebuje 45 kroków – 9 wezwań do odwrócenia, a następnie 36 wezwań do dołączenia. Jedną z rzeczy którą można powiedzieć na korzyść algorytmu jest to, że lubi wymienność niewiadomych – przynajmniej przy pierwszym rozwiązaniu każdego pytania. Lecz jeżeli pierwszy argument jest niezainicjowany, drugi argument jest listą i prosimy o więcej niż jedno rozwiązanie zaczynają się dziać różne dziwne rzeczy.

32 Szybsze odwracanie list Oto algorytm który odwraca listy o wiele szybciej ale kosztem możliwości wymiany niewiadomych. fast_reverse(Original,Result) :- nonvar(Original), fast_reverse_aux(Original,[],Result). fast_reverse_aux([Head|Tail],Stack,Result) :- fast_reverse_aux(Tail,[Head|Stack],Result). fast_reverse_aux([],Result,Result). Pierwsza klauzula sprawdza czy oryginalna lista rzeczywiście jest zainicjowana, następnie wywołuje trzy argumentową procedurę nazywaną fast_reverse_aux. Ideą jest przesuniecie elementów jeden po drugim, podnosząc je z początku oryginalnej listy i dodawanie ich do nowej listy służącej jako stos. Nowa listas taje się odwróconą kopią oryginalnej listy. Przez wszystkie wezwania rekurencyjne, Result(wynik) jest niezainicjowany, na końcu zainicjujemy go i przekazujemy z powrotem do procedury wezwań.

33 ?- fast_reverse_aux([a,b,c],[],Result). ?- fast_reverse_aux([b,c],[a],Result). ?- fast_reverse_aux([c],[b,a],Result). ?- fast_reverse_aux([],[c,b,a],[c,b,a]). Ten algorytm odwróci n-elementową listę w n+1 krokach. W pierwszej klauzurze załączyliśmy nonvar by fast_reverse zawiodło jeżeli pierwszy argument jest niezainicjowany. Bez tego niezainicjowany argument wprowadził by komputer w niekończące się obliczanie, tworząc dłuższe i dłuższe listy niezainicjowanych zmiennych która żadna nie była by rozwiązaniem.

34 Ciąg znaków Są trzy sposoby na zaprezentowanie ciągu znaków w Prologu: 1) Jako atom. Atomy są kompaktowe ale ciężkie do rozdzielenia lub manipulowania. 2) Jako lista kodów ASCII. Możesz użyć na nich standardowej listy technik przetwarzania. 3) Jako lista jedno znakowych atomów. Ponownie możesz użyć na nich standardowej listy technik przetwarzania. W Prologu jeśli wpiszesz znak w cudzysłowiu („tak jak tu”),komputer interpretuje to jako listę kodów ASCII. Tak więc „abc” i [97,98,99] są takie same w terminologii Prologu. Taki listy kodów ASCII tradycyjnie nazywane są Stringami.

35 Natychmiastowym problemem jest to że nie ma standardowego sposobu na wyświetlenie znaku, ponieważ write i display wypisują listę numerów: ?- write("abc"). [97,98,99] Yes Prosta procedura wyświetlania stringów: write_str([Head|Tail]) :- put(Head), write_str(Tail). write_str([]). Rekurencja jest prosta do śledzenia. Jeżeli string nie jest pusty (tak więc będzie się zgadzał [Head|Tail]) wypisze pierwszą pozycje 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 i tak dalej.

36 Zdefiniuj Prologa przewidź print_split który, gdy otrzyma stringa, wypisze wszystkie możliwe sposoby podziału stringu na dwa tak jak to: ?- print_splits("university"). university yes

37 Wprowadzenie linii jako string lub atom Łatwo sprawić by Prolog czytał całą linie wprowadzeń do pojedynczego stringa. Ideą jest unikanie używania read, a zamiast tego używania get 0 do wprowadzania znaków dopóki nie dojdziemy do końca linii. Okazuje się że algorytm potrzebuje jednego znaku LOOKAHEAD- nie może zdecydować co zrobić z każdym znakiem dopóki nie będzie wiedział czy kolejny znak jest znacznikiem końca linii. Wiec oto sposób jak to zrobić % read_str(String) % Accepts a whole line of input as a string (list of ASCII codes). % Assumes that the keyboard is buffered. read_str(String) :- get0(Char), read_str_aux(Char,String). read_str_aux(-1,[]) :- !. % end of file read_str_aux(10,[]) :- !. % end of line (UNIX) read_str_aux(13,[]) :- !. % end of line (DOS) read_str_aux(Char,[Char|Rest]) :- read_str(Rest). Zauważ ze przewidywanie zaczyna się krótkim komentarzem opisującym to. Od teraz taki komentarz będzie naszą standardową praktyką.

38 Lookahead jest osiągnięty przez przeczytanie jednego znaku, następnie przekazaniem tego znaku do read_str_aux, który dokonuje decyzji, a potem kończy wpisywanie w linii. Konkretnie: 1) Jeżeli Char jest 10 lub 13 (koniec linii) lub -1 (koniec pliku), nie wkładaj nic więcej, reszta stringu jest pusta. 2) W przeciwnym wypadku postaw Char na początku stringu i rekurencyjnie wkładaj jego resztę w ten sam sposób. Cięcia w read_str_aux zapewniają że jeżeli jakakolwiek z pierwszych trzech klauzul zakończy się powodzeniem ostatnia klauzula nie będzie nigdy wypróbowywana. Celem cięć w tym przypadku jest zachowanie ostatniej klauzuli przed dopasowywaniem nieodpowiednich wartości Char-ów. Zauważcie ze read_str zakłada ze input klawiatury jest buforowany. Jeżeli klawiatura jest nie buforowana read_str będzie wciąż działał, ale jeżeli użytkownik użyje backspace podczas wpisywania backspace nie usunie poprzedniego znaku – zamiast tego znak backspace-u pojawi się w stringu.

39 Struktury Wiele warunków Prologa składa się z funktora wynikającego zera lub więcej warunków takich jak argumenty: 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.

40 Dotąd używaliśmy struktur w faktach, zasadach, zapytaniach i wyrażeniach algebraicznych. Struktury są również pozycjami danych w ich własnych prawach. Dla przykładu: 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))))

41 Struktury działają jak listy, jednakże są inaczej przechowywane (i bardziej kompaktowo) w pamięci. Struktura a(b,c) zawiera te same informacje jak lista [a,b,c]. W rzeczywistości oba są wymienialne przez ‘=..’ ?- a(b,c,d) =.. X. X = [a,b,c,d] yes ?- X =.. [w,x,y,z]. X = w(x,y,z) Yes ?- alpha =.. X. X = [alpha] Yes Zauważcie ze po lewej stronie argument jest zawsze strukturą, kiedy prawa strona argumentu jest zawsze listą której pierwszy element jest atomem.

42 Jedną ważną różnicą jest to że lista jest rozkładana na head i tail, kiedy struktura nie jest. Struktura zjednoczy się z inna strukturą która ma taki sam funktor i tą samą liczbę argumentów. Oczywiście cała struktura również zjednoczy się z pojedynczą zmienna: 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 Ćwiczenie Używając tego co już znasz o listach wykonywania, stwórz orzeczenie reverse_args które bierze dowolną strukture i odwraca kolejność jej argumentów: ?- reverse_args(a(b,c,d,e),What). What = a(e,d,c,b)

43 Occurs check Można stworzyć dziwną, zapętloną strukturę by ujednolicić zmienne z strukturą lub listą która zawiera tę zmienną. Takie struktury zawierają wskaźniki do nich samych i prowadzą do niekończących się pętli kiedy wydruk staję się rutyną lub cokolwiek innego próbuje pokrzyżować je. Dla przykładu: ?- 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(...

44 Standard ISO zawiera orzeczenie unify_with_occurs_check które sprawdza którykolwiek jeden termin zawierający inny przed próbą ujednolicenia i zawodzi, jeśli tak to: ?- unify_with_occurs_check(X,f(X)). no. ?- unify_with_occurs_check(X,f(a)). X = f(a) Nasze doświadczenie jest takie ze occurs-check jest rzadko potrzebny w praktycznych programach Prologa, jednak jest czymś czego powinniśmy byś świadomi.

45 Konstruowanie celów na starcie Ponieważ zapytania w Prologu są strukturami, możesz je traktować jako dane i konstruować je podczas uruchomienia programu. Wbudowane orzeczenie call wykonuje swoje argumenty jako pytanie. Tak więc call(write(‘hello there’)) jest równoznaczne z write(‘hello there’). Siła call-a pochodzi z faktu ze cel może być stworzony przez obliczenie i wykonanie. Dla przykładu: answer_question :- write('Mother or father? '), read_atom(X), write('Of whom? '), read_atom(Y), Q =.. [X,Who,Y], call(Q), write(Who),

46 Jeżeli użytkownik wpisze mother i cathy wtedy Q stanie się mother(Who,cathy). Jest to wtedy wykonywane jako pytanie a wartość Who jest wypisana. Tak więc: ?- answer_question. Mother or father? father Of whom? michael charles_gordon yes ?- answer_question. Mother or father? mother Of whom? melody eleanor yes

47 Możemy zrobić to nieco bardziej wygodne przez zdefiniowanie orzeczenia Apple (podobne do APPLY w Lisp) które bierze atom oraz listę i tworzy zapytanie używając atomu jako funktora a listy jako argumentów, następnie wykonuje zapytanie. % apply(Functor,Arglist) % Constructs and executes a query. apply(Functor,Arglist) :- Query =.. [Functor|Arglist], call(Query). Cel apple(mother,[Who,melody]) ma ten sam efekt co mother(who,melody). Argumenty dawane są przez listę ponieważ ich liczba jest nieprzewidywalna. Prolog zapobiega zdefiniowaniu orzeczenia z dowolną zmienną liczbą argumentów.

48 Strategie przechowywania danych Istnieją trzy miejsca w których mogą być przechowywane dane w programie Prolog: 1) W instancji zmiennej. Jest to mniej trwały sposób przechowywania informacji ponieważ, zmienne istnieją tylko w zasięgu klauzuli która je definiuje. Dalej zmienne tracą swoje wartości na rzecz backtracking-u. 2) W argumentach orzeczeń. Lista argumentów jest jedynym sposobem by procedura Prologa normalnie komunikowała się ze światem. Przez przekazywanie argumentów do siebie kiedy wzywa się rekurencyjnie, procedura może wykonać rekurencyjny proces i zapisać informacje z jednej rekurencji do następnej. 3) W bazie wiedzy. Jest to najbardziej trwały sposób przechowywania informacji. Informacje umieszczone w bazie wiedzy przez asserta lub assertz pozostaje tam dopóki wyraźnie je wycofamy.

49 Prostym przykładem przechowywania wiedzy w bazie wiedzy jest orzeczenie count które mówi ci ile razy zostało wywołane. Dla przykładu: ?- count(X). X = 1 yes ?- count(X). X = 2 Yes % count(X) % Unifies X with the number of times count/1 has been called. count(X) :- retract(count_aux(N)), X is N+1, asserta(count_aux(X)). :- dynamic(count_aux/1). count_aux(0).

50 Ponieważ count musi zapamiętać informacje z jednego wezwania do następnego bez względu na backtracking lub niepowodzenie, musi przechowywać dane w bazie wiedzy używając assert i retract. Nie ma możliwości by informacje mogły przejść z jednej procedury do innej przez argumenty, ponieważ nie ma możliwości przewidzenia jaka będzie ścieżka wykonywania. Jest kilka powodów by używać assert tylko jako środek ostateczny. Pierwszym jest to ze assert przeważnie zabiera więcej czasu komputerowi niż zwykłe przekazywanie argumentu. Innym powodem jest to że programy używające assert są trudniejsze do zdebugowania. Są jednak słuszne powody używania assert. Jednym z nich jest zapisywanie wyników które zajmują dużo czasu i miejsca do przeliczenia. Dla przykłady algorytm przeszukujący grafy może wykonać ogromną ilość kroków by znaleźć każdą ścieżkę grafu.

51 Kolejnym słusznym użyciem assert jest ustawienie parametrów kontrolnych dużego skomplikowanego programu takiego jak eksperckiego systemu którego użytkownik może używać w kilku trybach. Przez wykonywanie właściwych assert-ów program może ustawić się do wykonywanie funkcji które użytkownik chce wykorzystać w szczególnych sesjach.

52 Dziękujemy za uwagę


Pobierz ppt "Struktury danych i obliczenia w Prologu Autorzy: Kamil Krajewski Mateusz Szymański Paweł Bińkowski."

Podobne prezentacje


Reklamy Google