Prolog cz. III

Zanim przejdziemy do tak ważnej części w Prologu jaką jest przetwarzanie list. omówimy jeszcze dwa warunki: forall oraz or.

Konstrukcja forall ma formę:

(forall C then C)

Są to wszystkie lokalne zmienne spełniające warunek C, a ponadto C. Odpowiednikiem forall jest podwójna negacja: not (C and not (C) )

Foral C then C ma wartość logiczną True gdy wszystkie zmienne spełniające warunek C spełniają także C. Implikacja odwrotna nie musi byc oczywiście spełniona.

Przykładem użycia forall może być następująca definicja podzbioru

X podzbiorem Y if

(forall x belongs-to X then x belongs-to Y)

Warunek or służy do łączenia kilku części jednej definicji. Pamiętamy regułę określającą czy zmienna x jest rodzicem

x parent-of y if x father-of y

x parent-of y if x mother-of y.

Or łączy nam to w jedną zwartą regułę:

x parent-of y if (either x father-of y or x mother-of y)

Or używane jest w formie (either C or C),

gdzie C i C są pojedynczymi lub złożonymi warunkami. Równie łatwo możemy ułożyć przejrzystą i prostą definicję definicji belongs-to: Zamiast

x belongs-to (x/y)

x belongs-to (y/z) if x belongs-to z

piszemy po prostu:

x belongs-to (y/z) if (eitcher x EQ y or x belongs-to z)

 Jedną z najbardziej fascynujących możliwości PROLOGU jest operowanie listami. Podstawową relacją służącą do pracy na listach jest APPEND, zdefiniowana następująco:

APPEND (x y z) zachodzi wtedy, gdy lista z jest połączeniem listy x oraz y. Dla przykładu podam kilka list przed i po połączeniu:

(A B C D) i (E F G) daje (A B C D E F G)

( ) i (A B) daje (A B)

(a) i ((a) (b c)) daje (a (a) (b c)) itd.

Dzięki APPEND możemy zdefiniować na nowo bardzo Użyteczną relację belongs-to, której definicja poprzednio wyglądała następująco:

()belongs-to()

X belongs-to (y z) if

(either x EQ y or x belongs-to z)

Obecnie możemy tę definicję znacznie uprościć: y belongs-to Z if APPEND (x (y|Y) Z)

czyli y jest elementem zbioru (listy) Z jeżeli zbiór Z możemy utworzyć przez połączenie jakiejś listy X i listy (y)Y) o pierwszym elemencie równym y. Dodam, ze skonstruowana w ten sposób definicja jest znacznie szybsza od poprzedniej, gdyż nie jest rekurencyjna i operuje na oryginalnej relacji z PROLOGU. Wykorzystując tę relację możemy utworzyć wiele bardzo użytecznych definicji. Możemy najpierw zdefiniować pierwszy i ostatni element listy.

first (x Y) if (first — pierwszy)

APPEND ((x) Z Y)

pamiętajmy, że w tej relacji x jest elementem.

zaś Z. Y listami.

Gdybyśmy zamiast (x) napisali x to na pytanie which x: first (x (a b c). Prolog odpowiedziałby: (a), czyli nie byłaby to już definicja pierwszego elementu, ale pierwszej składowej jednoelemen-towej listy. Gdy piszemy (x) to PROLOG „wie", że (x) jest listą czyli x jest jej elementem. Podobna jest definicja ostatniego elementu:

last (x y) if (last — ostatni)

APPEND (z (x) y)

Odpowiedzią na pytanie:

which (x y:APPEND (x y (a b c d e f))) jest

() (a b c d e f)

(a) (b c d e f)

(a b) (c d e f)

(a b c) (d e f)

(a b c d) (e f)

(a b c d e) (f)

(a b c d e f) ()

Dlatego, że są to wszystkie możliwe połączenia dwóch list, aby uzyskać listę (a b c d e f).

Wydawać by się mogło, że konieczne jest jeszcze jedno ograniczenie w definicji „first" i „last" na to, by lista (x) miała tylko jeden element. Lecz pamiętajmy, że przecież sam zapis (x) oznacza listę jednoelementową.Jedną z najistotniejszych definicji jest front (x y z), która jest prawdziwa wtedy i tylko wtedy, gdy lista y stanowi x pierwszych elementów listy z,

front (x y z) if APPEND (y y1 z) and x length-of y.

Obrazowo mówiąc relacja front wybiera z wszystkich rozbić listy z na y i y1, takie w którym y ma dokładnie x elementów.

which (x: front( 3 x (A B C D E F G)))

(ABC)

Ciekawe jest rozdzielanie listy na składowe segmenty: dwie relacje (xlX) initial-segment-of z if APPEND (xlX)

(yz)

(ylY) back-segment-of z if APPEND (x (yl Y) z)

dają po złożeniu według definicji:

x segment-of z if y back-segment-of z and

x initial-segment-of y relację segment-of (x z), która rozdziela listę na pod-listy

which (x: x segment-of (A B C D)) (A) (A B)

(ABC)

(A B C D)

(B)

(B C)

(B C D)

(C)

(C D)

(D)

Relacja taka jest konieczna do utworzenia innej niezwykle istotnej relacji power-set (zbiór potęgowy)

Zbiorem potęgowym zbioru x nazywamy zbiór podzbiorów utworzonych przez połączenie na wszystkie sposoby elementów X. Zbiór potęgowy oznaczamy symbolem 2X.

power-set (x(())y)) if y isall (z: z segment-of x)

Na przykład:

which (x: power-set( (a b c) x)) (0 (c) (b c) (b) (a b c) (a b) (a c) (a))

Kolejną relacją, na którą warto zwrócić uwagę, jest reverse (x y). Relacja ta pobiera listę y i odwraca ją, tworząc tym samym listę x,

(x) reverse (x)

(x y|X) reverse z if (y|X) reverse Y and APPEND (Y (x) z)

Jak widzimy jest również rekurencyjna postać definicji

which (x: (A B C D E) reverse x) (E D C B A)

Jeszcze jedną relacją, na którą pragnąłbym przybliżyć czytelnikom, jest nth-el. Jej wartością logiczną TRUE (nth-el (x y z) = TRUE) wtedy, gdy element y jest x-tym elementem listy z.

nth-el (X Y Z) if

x length-of Z and (1)

(either X LESS x or X EQ x) and (2)

APPEND (y (Y/z) Z) and (3)

X1 length-of y and (4)

SUM (X1 1 X). (5)

Ponieważ jest to relacja ciekawa, a jej definicja bardzo pouczająca, więc pokrótce ją zanalizujemy.

Na początku zakładamy, że parametrami tej relacji będą po kolei liczba element i lista. W punkcie (1) definiujemy długość listy z, będzie ona równa x. Warunek (2) polega na sprawdzeniu czy numer elementu, który pragniemy poznać nie wykracza poza rozmiar listy, czyli X mniejsze lub równe x. Dalej w (3) rozbijamy listę Z na dwie składowe, z których pierwsza ma długość X-1 (warunek (4) i (5)). Z zapisu APPEND (y (Y/z) Z) wynika, że jeżeli lista y ma długość X-1, (czyli zawiera elementy o numerach od 1 do X-1), to lista (Y/z) ma długość x-X i numery jej poszczególnych elementów to X, X + 1, ...x. W szczególności pierwszy element listy (Y/z) ma numer X (ten, o który nam chodziło). Stąd już prosty wniosek, że X-tym elementem jest głowa listy (Y/z), czyli Y. On też jest odpowiedzią w relacji nth-el (X Y Z).

Ułóżmy relację, która będzie permutowała dowolną listę. Najpierw utworzymy relację pomocniczą „del".

del(x X Y) zachodzi gdy lista Y jest listą X zubożoną o jeden dowolny element x.

del (x X Y) if APPEND(X1 (x|X2) X) and AP-PEND(X1 X2 Y)

Działanie relacji „del" obrazuje następujący przykład:

which (x: del(x (a b c) (a c))) b

No (more) answers.

Definicja permutation-of wygląda następująco:

Opermutation-of) (yjY) permutation-of X if del (y X Z) and Y permutation-of Z.

which (X: X permutation-of (a b c))

(abc)

(acb)

(bac)

(bca)

(cab)

(cba)

Na zakończenie pragnąłbym zaznajomić Czytelników ze składnią oryginalnego PROLOGU. Każda definicja napisana przez nas ma swój odpowiednik w oryginalnym PROLOGU. Pokażę teraz jak każda z definicji wprowadzonych dzisiaj wygląda w zapisie listowym,

belonges-to:

((belonges-to(H))

((belonges-to X (Y|Z))

(OR(( EQ X Y)) ((belones-to X Z))))

first:

((first X Y)

(APPEND (X) Z Y))

front:

((front X Y Z)

(APPEND Y x Z) (LENGTH-OF X Y)

segment-of:

((segment-of X Y)

(back-segment-of Z Y) (initial-segment-of X Z) power-set: ((power-set X(()|Y))

(ISALL Y Z (segment-of Z X))) reverse; ((reverse (X) (X))) ((reverse (X Y Z) x) (reverse (Y Z) y) (APPEND y (X) x)) nth-el: ((nth-el X Y Z) (lenght-of x Z)

(OR ((LESS X x)) ((EQ X x))) (APPEND y (Y z) Z) (lenght-of X1 y) (SUM X1 1 X))

del:

((del X Y Z)

(APPEND x (X|y) Y) (APPEND x y Z))

permutation:

((permutation-of()()))

((permutation-of (X Y) Z) (del X Z x) (perm Y x))

Już z pobieżnych obserwacji wynika, że w oryginalnej pisowni cała definicja jest listą. Początek tej listy to nazwa definicji wraz z parametrami. Każda następna lista stanowi jakiś warunek. Czytając tak zapisaną definicję moglibyśmy wstawić między pierwszy a drugi element „if", a między każcie kolejne „and". Te części listy, które są względem siebie alternatywne, umieszczamy w liście (OR ((...)) ((...))..((...))) Poza tym PROLOG posługując się w definicji pewnej relacji definicją innej relacji, nie rozbija tej drugiej na pewne pierwotne zastrzeżone symbole, lecz przedstawia ją w postaci nazwy i listy parametrów. Postępowanie takie zwiększa z pewnością czytelność programu, gdyż każda definicja jest strukturalizowana, nie wzmaga jednak prędkości jej przetwarzania.

Na tym kończymy wstępny kurs programowania w microPrologu. Tym, których zainteresował język logiki obiecujemy, że będziemy wracać w Bajtku do tego tematu.

 

Adam Krauze