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)