Instrukcja projektowa z Programowania w Logice
Transkrypt
Instrukcja projektowa z Programowania w Logice
Języki Programowania Projekt 4: Programowanie w logice Środowisko ECLiPSe W projekcie wykorzystane będzie środowisko ECL iPSe. Dostępne jest ono pod adresem http://eclipseclp.org/. Po zainstalowaniu środowiska będziemy mieli dostępne dwie jego wersje: konsolową (dla systemu Windows jest to <kataloginstalacji>\lib\i386_nt\eclipse.exe) i graficzną (TkEclipse). Po utworzeniu pliku z programem (w ulubionym edytorze lub przy pomocy File->Edit new...) możemy go skompilować poleceniem File->Compile... lub wpisując [<nazwa-pliku-bez-rozszerzenia>]. w polu Query Entry (w tym wypadku plik powinien mieć rozszerzenie .pl i znajdować się w bieżącym katalogu). Zapytania wpisujemy w polu Query Entry. Programowanie w logice Program w języku PROLOG składa się z zestawu definicji klauzul. Najprostsza definicja klauzuli p/1 (1 oznacza liczbę parametrów klauzuli) ma postać: p(1). Oznacza ona, że klauzula ta jest spełniona dla parametru równego 1 (i tylko dla takiego). Jeżeli teraz wykonamy zapytanie: p(X). otrzymamy odpowiedź: X = 1 Yes Po zadaniu zapytania PROLOG przegląda wszystkie zdefiniowane klauzule i próbuje dopasować do nich zapytanie. Na przykład, gdy p jest spełnione dla 1 i 2: p(1). p(2). > p(X) X = 1 \\ Yes, maybe more X = 2 \\ Yes, maybe more Ogólna definicja klauzuli ma postać: predykat(Zmienne) :- cel. i oznacza, że klauzula dla zmiennych Zmienne jest spełniona, jeżeli spełniony jest cel, przy czym cel jest listą pod-celi połączonych koniunkcją (w postaci cel-1, cel2, ..., cel-n) lub alternatywą (w postaci (cel-1; cel-2; ...; cel-n)). Np. klauzula: q( 0,0 ). q( X,Y ) :- X > 0, T is X – 1, q( T,Y ). jest spełniona dla parametrów 0,0 oraz dla wszystkich X,Y takich, że q( X-1,Y ) jest spełnione. Do obliczenia wartości wyrażenia potrzebna jest klauzula is/2. W przeciwnym wypadku zmiennej logicznej T zostałoby przypisane wyrażenie symboliczne X – 1. Klauzule postaci p(X). (tzn. z pustą listą celów) nazywamy faktami, pozostałe klauzule – regułami. Zadajmy teraz zapytanie q( 7,0 ). Nie udało się go dopasować do pierwszej klauzuli, jednak dopasowanie do drugiej powiedzie się: zmienna X zostanie związana z wartością 7, Y zaś z zerem, po czym nastąpi sprawdzenie prawdziwości pod-celi. Pierwsze dwa są spełnione. Aby sprawdzić prawdziwość trzeciego pod-celu, należy sprawdzić, czy p( 6,0 ) jest prawdziwe. Nastąpi ponowna próba dopasowania zapytania (tym razem p( 6,0 )). Będzie ono prawdziwe, jeżeli prawdziwe będzie p( 5,0 ) itd., aż do zapytania p( 0,0 ), które jest prawdziwe z „definicji” (pierwszy fakt). Listy W PROLOGu często wykonujemy operacje na listach. Lista jest uporządkowanym ciągiem elementów zawartych między nawiasami kwadratowymi i rozdzielonych przecinkami, np.: [ 1,2,3,5 ]. Szczególnym przypadkiem jest pusta lista, oznaczana przy pomocy []. Każda niepusta lista skonstruowana jest z głowy (pierwszego elementu) i ogona (listy pozostałych elementów, potencjalnie pustej). Konstrukcja listy z głowy H i ogona T ma postać [H|T]. Zatem lista [ 1,2,3 ] może być zapisana na jeden z równoważnych sposobów: [ 1,2,3 ] [1|[ 2,3 ]] [1|[2|[3]]] [1|[2|[3|[]]]] Przykładowy predykat, wyznaczający długość listy ma postać: dlugosc( [],0 ). dlugosc( [_|T],L ) :- dlugosc( T,P ), L is P + 1. czyli długością pustej listy jest zero, zaś lista niepusta ma długość o jeden większą niż długość jej ogona. _ oznacza, że głowy listy nie wiążemy z żadną zmienną logiczną. Kolejny przykład – dołączenie jednej listy na koniec drugiej: dolacz( [],L,L ). dolacz( [H|T],L,[H|X] ) :- dolacz( T,L,X ). Lista L dołączona do pustej listy daje listę L. Lista L dołączona na koniec listy składającej się z głowy H i ogona T tworzy listę zbudowaną z głowy H i ogona X będącego wynikiem dołączenia na koniec T listy L. Kolejny przykład – sprawdzenie, czy X jest równy jednemu z elementów listy: nalezy( X,[X|_] ). nalezy( X,[H|T] ) :- nalezy( X,T ). Komentarze Komentarze w PROLOGu zawarte są pomiędzy /* i */ (jak w C) lub pomiędzy % a końcem linii, np.: p(1). /* to jest komentarz */ p(2). % to tez jest komentarz Programowanie z ograniczeniami Aby wykorzystać programowanie z ograniczeniami, należy zaimportować bibliotekę ic, przy pomocy polecenia (podanego na początku programu): :- lib(ic). Typowe rozwiązanie problemu przy użyciu CLP ma postać: rozwiaz(Zmienne):- wczytaj_dane(Dane), ustaw_ograniczenia( Dane,Zmienne ), labeling(Zmienne). gdzie ustaw_ograniczenia/2 definiuje model problemu. Predykat labeling/1 próbuje znaleźć rozwiązania sprawdzając wszystkie podstawienia dla zmiennych. Rozważmy następujący problem, w którym mamy 8 zmiennych: S, E, N, D, M, O, R, Y, każda z nich oznacza inną cyfrę, oraz spełniona jest równość: S E N D + M O R E ––––––––––– = M O N E Y Rozwiązanie tego problemu będzie miało postać: :- lib(ic). model(Zmienne) :Zmienne = [ S,E,N,D,M,O,R,Y ], Zmienne :: 0..9, alldifferent(Zmienne), S #\= 0, M #\= 0, 1000 * S + 100 * E + 10 * N + D + 1000 * M + 100 * O + 10 * R + E #= 10000 * M + 1000 * O + 100 * N + 10 * E + Y. rozwiaz(Zmienne) :- model(Zmienne), labeling(Zmienne). Predykat model najpierw tworzy listę zmiennych w naszym problemie. W drugim kroku przypisujemy wszystkim zmiennym (przy pomocy predykatu ::/2) jako domenę zbiór liczb naturalnych z przedziału <0, 9>. Aby stworzyć domenę będącą podzbiorem liczb rzeczywistych, przedział 0..9 należałoby zmienić na 0.0..9.0 lub użyć predykatu $::/2. W następnej linii określamy, że wszystkie zmienne muszą być różne. Na koniec stwierdzamy że ani S ani M nie mogą być równe zero (ograniczenia wyrażone operatorami porównania tworzy się dodając znak # przed operatorem) oraz opisujemy nasze równanie. Zadania 1. Zaimplementuj predykat czy_posortowane( lista,odp ), stwierdzający, czy lista jest uporządkowana rosnąco (1 pkt.). Np.: > czy_posortowane( [1,2,3],ODP ) ODP = T > czy_posortowane( [6,2,3],ODP ) ODP = N 2. Zaimplementuj predykat czy_graficzny( lista,odp ), stwierdzający, czy lista tworzy ciąg graficzny (2.5 pkt.). 3. W wariancie gry Nim mamy 3 stosy kamieni. Dwóch graczy na przemian zdejmuje kamienie ze stosów i ten który wykona ostatni ruch (po jego ruchu nie zostaną żadne kamienie, lub drugi gracz nie może wykonać dozwolonego ruchu) przegrywa. Zaimplementuj predykat czy_wygrywa( A,B,C ) stwierdzający, czy gracz, który wykonuje ruch dla odpowiednio A, B i C kamieni na stosach ma strategię wygrywającą. (4 pkt). Dozwolone ruchy dla graczy to (według ostatniej cyfry numeru indeksu): 0) zdjęcie jednego lub dwóch kamieni z dowolnego stosu, 1) zdjęcie dwóch lub trzech kamieni z dowolnego stosu, 2) zdjęcie jednego kamienia z dowolnego stosu lub dwóch kamieni z różnych stosów, 3) zdjęcie jednego kamienia z dowolnego stosu i jednego kamienia z pierwszego stosu lub dwóch kamieni z dowolnego stosu, 4) zdjęcie po jednym kamieniu z każdego stosu lub zdjęcie dwóch kamieni z dowolnego stosu, 5) zdjęcie dwóch kamieni z drugiego stosu lub jednego kamienia z dowolnego stosu, 6) zdjęcie jednego kamienia z dowolnego stosu i przełożenie jednego kamienia ze stosu pierwszego na drugi lub zdjęcie dwóch kamieni z dowolnego stosu, 7) zdjęcie jednego kamienia z dowolnego stosu i przełożenie dwóch kamieni ze stosu pierwszego na trzeci lub zdjęcie dwóch kamieni z dowolnego stosu, 8) przełożenie jednego kamienia z pierwszego stosu na drugi i zdjęcie po jednym kamieniu z każdego stosu lub zdjęcie dwóch kamieni z drugiego stosu, 9) zamiana miejscami stosów drugiego i trzeciego i zdjęcie jednego kamienia ze stosu pierwszego, lub zdjęcie jednego kamienia z dowolnego stosu i jednego ze stosu trzeciego. Gracz A ma strategię wygrywającą, gdy niezależnie od ruchów przeciwnika, odpowiednio wybierając posunięcia, zawsze wygra. Dla danego układu kamieni gracz A może przejść do takiego układu, że niezależnie od tego, jaki ruch wybierze gracz B, gracz A wygra. Sytuację taką można przedstawić w postaci drzewa (Si to kolejne stany gry): S1 – gracz A S2 – gracz B i S5 – gracz A wygrywa S6 – gracz A wygrywa ... lub S3 – gracz B S4 – gracz B ... lub ... S7 – gracz A S8 – gracz B S9 – gracz B ... ... S10 – gracz B wygrywa Gdy gracz A wykonuje ruch, wystarczy, że tylko jeden z nich prowadzi do wygranej (LUB). Gdy gracz B wykonuje ruch – wszystkie jego ruchy muszą prowadzić do wygranej A (AND; gdyby było inaczej, B mógłby przejść do stanu gry, w którym A nie wygrywa). Literatura http://www.eclipse-clp.org/ ECLiPSe A Tutorial Introduction (http://87.230.22.228/doc/tutorial.pdf) ECLiPSe User Manual (http://87.230.22.228/doc/userman.pdf)