Prolog Praca z danymi zewnętrznymi Zastosowania

Transkrypt

Prolog Praca z danymi zewnętrznymi Zastosowania
Prolog
Praca z danymi zewnętrznymi
Zastosowania
Piotr Jarzyński, Krystian Marynowski, Arkadiusz Nowacki, Mateusz Lewandowski
PWSZ PŁOCK
Domyślnie w Prologu wszystkie wprowadzone dane są napisane w składni tego
języka. Założenie to jest utrudnia efektywną pracę. Niezbędne są algorytmy
umożliwiające pracę z danymi w innych formatach.
Odczytywanie danych
Wprowadzenie linii jako string:
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)
Konwersja stringa na stałą znakową:
read_atom(Atom) :read_str(String),
atom_codes(Atom,String).
Konwersja stringa na liczbę:
read_num(Atom) :read_str(String),
number_codes(Atom,String).
Zapożyczanie kodu z zewnętrznych źródeł
Często pisząc skomplikowane algorytmy chcemy zachować porządek w kodzie przechowując różne
części programu w odrębnych plikach. Załóżmy, że chcemy aby nasz program mógł korzystać
z kodu zawartego w readstr.pl
Wystarczy, że na początku naszego pliku dodamy:
:- ensure_loaded("readstr.pl").
A kompilator załaduje wybrany plik. Znaczy to dokładnie "Jeśli plik readstr.pl nie jest jeszcze
wczytany, wczytaj go teraz". Dzięki temu unikniemy niepotrzebnej duplikacji kodu.
Dostęp do plików
Wiele aplikacji wymaga aby wynik działania programu zapisany został do pliku. Omówimy jak
zapisać dane do pliku oraz jak je odczytać. Prolog pozwala na dostęp plików poprzez uchwyt,
wykorzystując predykat open np:
open('file.txt',read,Handle).
otworzy plik file.txt w trybie odczytu (lub do zapisu jeżeli drugi argument ustawimy na write)
następnie przypisuje uchwyt do Handle. Może on być od teraz wykorzystywany do wykonywania
operacji na pliku do czasu jego zamknięcia poprzez:
close(Handle).
Zapisywanie do pliku
Poniższa procedura pozwoli użytkownikowi dodać tekst do pliku:
appendLineToFile(FileName) :write('Append text to file '),
write(FileName),
write(': '),
read_str(X),
atom_codes(Atom,X),
open(FileName,append,FileStream),
nl(FileStream),
write(FileStream, Atom),
close(FileStream).
Odczyt pliku
Kolejna procedura wczyta plik i wyświetli na ekran:
processFile(FileName) :see(FileName),
processChar,
seen.
processChar :get0(Code),
processCode(Code).
processCode(-1):- !.
processCode(Code):- name(L,[Code]),
write(L),
processChar.
Pliki z rekordami o wartościach stałej długości
Pisząc programy często możemy spotkać się z plikami tekstowymi zawierającymi ustalone pola o
stałej długości. Pliki te często różnią się od siebie, mogą ale nie muszą kończyć się znakiem końca linii,
zawierać puste pola, mogą to być pliki tekstowe lub innego formatu. Odczytanie pliku rekordów o
polach stałej długości jest jednak stosunkowo proste. Zaczynamy od liczby bajtów która ma być
odczytana i odliczamy w dół do 0:
read_bytes(0,[]) :- !.
read_bytes(N,[C|Rest]) :get_byte(C),
NextN is N-1,
read_bytes(NextN,Rest).
Wersja read_bytes , której używamy jest bardziej skomplikowana. Wykorzystuje jeden znak
LookAhead aby sprawdzić nieoczekiwany znak na końcu pliku(end-of-file). Możemy również
zdefiniować skip_bytes, który pomija określoną liczba bajtów, bez zapisywania ich na liście.
Pliki CSV (wartości oddzielone średnikiem)
Odczyt plików, w których pola rekordu są oddzielone średnikiem (CSV - Comma Separated Value)
jest jeszcze prostszy z uwagi na wbudowaną w prologu procedurę:
csv_read_file(+File, -Rows, +Options)
Przykładowo aby odczytać dane z pliku commadel.dat wykonajmy:
csv_read_file('commadel.dat', Rows, [functor(record), arity(6)]).
W rezultacie otrzymamy listę Rows z rekordami o sześciu polach.
Przetwarzanie danych jako listy
Rozważmy poniższą bazę wiedzy:
ojciec(michał,kasia).
ojciec(andrzej,michał).
ojciec(daniel,monika).
Możemy wyświetlić imiona wszystkich ojców poprzez zapytanie:
?- father(X,_), write(X), nl, fail.
Czyli: znajdź X, dla którego ojciec(X, _), wypisz i zwróć fail aby znaleźć kolejne.
Jednak co w przypadku jeśli zamiast wyświetlania imion ojców, chcemy je dalej przetwarzać
jako lista? W celu uzyskania wszystkich imion, program musi wykonać powrót poprzez fail. Ale
w celu zbudowania listy musi użyć rekurencji przekazując częściowo zbudowaną listę jako argument
między iteracjami czego nie można zrobić korzystając z backtrakingu.
Z pomocą przychodzi nam wbudowany predykat findall/3:
findall(+Template, +Goals, -List)
Możemy go użyć w następujący sposób:
?- findall(X,father(X,_),L).
Czyli: znajdź wszystkie X, dla którego ojciec(X, _) i zbuduj z nich listę L.
„bagof” i „setof”
Największa różnicą między bagof i setof a findall jest to jak traktują one obsługę
niezainicjalizowanych zmiennych w "Goals", findall je pomija dlatego instrukcja:
?- findall(X,parent(X,Y),L).
Znajdzie, każdego kto jest rodzicem kogokolwiek. Y może mieć różne wartości dla każdego zrodziców.
Tymczasem:
?- bagof(X,parent(X,Y),L).
Znajdzie wszystkie wartości X, które są powiązane z jakąś konkretną wartością Y. Jeśli w miejscu Y
pojawią się różne wartości zostaną one zwrócone jako alternatywne rozwiązania zamiast
dodania ich do jednej listy.
Setof zachowuje się podobnie do bagof z tą różnicą, że wyniki są posortowane alfabetycznie oraz
kasowane są duplikaty. Operacja sortowania wymaga dodatkowego czasu czasu, ale może być
korzystna, ponieważ chroni nas przed duplikatami w późniejszej pracy.
parent(michael,cathy).
parent(melody,cathy).
parent(greg,stephanie).
parent(crystal,stephanie).
?- findall(X,parent(X,Y),L).
X = _0001, Y = _0002, L=[michael,melody,greg,crystal]
?- bagof(X,parent(X,Y),L).
X = _0001, Y = cathy, L = [michael,melody] ;
X = _0001, Y = stephanie, L = [greg,crystal]
?- setof(X,parent(X,Y),L).
X = _0001, Y = cathy, L = [melody,michael] ;
X = _0001, Y = stephanie, L = [crystal,greg]
Oczywiście „setof” jest jak „bagof” z wyjątkiem, że sortuje listy
i
usuwa
duplikaty
(jeśli
jakiekolwiek
występują).
Ale jest inny sposób. Można zrobić to:
?- bagof(X,Y^parent(X,Y),L).
X = _0001, Y = _0002, L = [michael,melody,greg,crystal]
Znajdowanie najmniejszego, największego, lub „najlepszego” rozwiązania
"Najlepsze" oznacza różne rzeczy w różnych sytuacjach, ale
podstawową ideą jest to, że ze wszystkich możliwych
rozwiązań, chcesz to, które przewyższa wszystkie inne
zgodnie z pewnymi kryteriami. Istnieją trzy główne metody:
 Użyć „setof” i wykorzystać wbudowany proces sortowania,tak że
"najlepsze" rozwiązanie wychodzi na początku (amoże na końcu)
listy;
 Użyć „bagof” lub „setof” a następnie pracować z listą ,
abywybrać rozwiązanie, którego potrzebujesz;
 Szukaj bezpośrednio „najlepszego” rozwiązania, porównując każdą
alternatywę na tle wszystkich pozostałych
Popracujmy z następującą bazą danych:
age(cathy,8).
age(sharon,4).
age(aaron,3).
age(stephanie,7).
age(danielle,4).
Które dziecko jest najmłodsze? Spróbujemy pierwszej i trzeciej strategii, pozostawiając
drugą jako ćwiczenie. Jest to łatwe do wykonania. „setof” daje nam wiek
najmłodszego dziecka. Weźmy pod uwagę te zapytania:
?- setof(A,N^age(N,A),L).
L = [3,4,7,8]
?- setof(A,N^age(N,A),[Youngest|_]).
Youngest = 3
Pierwsza kwerenda pobiera posortowaną listę wieku dzieci; druga kwerenda pobiera
tylko pierwszy element tej listy.
Definiowanie operatorów
Większość funktorów w Prologu jest zapisana bezpośrednio przed nawiasami zawierającymi
argumenty: functor(arg1, arg2). Funktory, które mogą być napisane w inny sposób nazywane
są operatorami.
Na przykład, struktura +(2,3) może być zapisana jako 2+3, ponieważ jej funktor + Jest
operatorem.
Nie należy mylić operatorów z operacjami. Niektóre operatory oznaczają arytmetyczne
operacje(+-*/), ale inne operatory służą zupełnie innym celom. W rzeczywistości każdy funktor
w Prologu może być zadeklarowany jako operator zmieniając w ten sposób swoją składnie,
ale nigdy nie znaczenie.
Powszechnie predefiniowane operatory w
PROLOGu.
Pierwszeństwo
Specyfikator
Operatory
1200
xfx
:- -->
1200
fx
:- ?1100
xfy
;
1050
xfy
->
1000
xfy
,
900
fy
\+ (lub w niektorych
Prologach, not)
700
xfx
= \= == \== @< @=< @> @>= is =:= =\= <
=< > >= =..
500
yfx
+400
yfx
* / // mod
200
xfy
^
200
fy
-
Specyfikatory operatorów
Specyfikator
Fx
Fy
Xf
Yf
Xfx
xfy
yfx
Znaczenie
Prefix, not associative
Prefix, right-associative (like \+)
Postfix, not associative
Postfix, left-associative
Infix, not associative (like =)
Infix, right-associative (like the comma in compound goals)
Infix, left-associative (like +)
Nadawanie znaczeń operatorom
Definicje operatora określają jedynie jego składnie. Znaczenie lub semantyka operatora zależy już od
programisty.
W przeszłości niektóre Prologi używały ampersandu (&) zamiast przecinka aby łączyć elementy
funkcji
złożonych . Jest to wielką zaletą, ponieważ złożone funkcje nie wyglądają jak listy argumentów;
zapytanie
?- call(p(a) & q(a) & r(a)).
wyraźnie wywołuje „call” tylko z jednym argumentem. W zwykłym Prologu, musimy użyć
dodatkowych
nawiasów, aby uzyskać ten sam efekt, jak tutaj:
?- call( (p(a), q(a), r(a)) ).
ponieważ bez dodatkowych nawiasów, „call” będzie traktowane jako posiadające trzy argumenty.
Możemy zdefiniować ampersanda aby pracował w ten sposób nawet w zwykłym Prologu.
Najpierw zdefiniujmy jego składnię. Chcemy aby & był operatorem wrostkowym z nieco niższym
priorytetem niż przecinek, dzięki czemu f (& b, c) będzie oznaczać f ((& b), c),a nie f (i (b, c)).
Ponadto, jak wkrótce zobaczymy, & powinno być w prawo-asocjacyjne. Tak więc, w większości
Prologów stosowną definicją operatora będzie:
:- op(950,xfy,&).
Następnie musimy powiedzieć Prologowi jak rozwiązać funkcję zawierającą ampersandy. Oczywiście
zapytanie GoalA & GoalB powinno zadziałać jeżeli GoalA zadziała a następnie GoalB zadziała z tymi
samymi instancjami. Czyli:
GoalA & GoalB :- call(GoalA), call(GoalB).
To jest po prostu zwykła reguła Prologa. Równie dobrze może to być zapisane jako:
'&'(GoalA,GoalB) :- call(GoalA), call(GoalB).
PROLOG W PROLOGu
Nasza definicja ampersandu „&” sugeruje strategię przepisania całego mechanizmu inferencji Prologa
do Prologa, aby rozwinąć tym samym zmodyfikowaną wersję języka. Przypomnijmy, że predykat
clause(Head, Body) może odzyskać jakąkolwiek z klauzul w bazie danych, a przynajmniej te, które są
zadeklarowane dynamicznie; Robi to, próbując połączyć Head z nagłówkiem klauzuli oraz body z
ciałem klauzuli(lub z „true” jeśli klauzula jest faktem). Klauzule alternatywne są otrzymywane jako
wielokrotne rozwiązania „clause” przy pomocy backtrackingu.
Ciało reguły jest zazwyczaj funkcją złożoną(compound goal), czyli struktura utrzymywana jest przy
pomocy
prawostronnych(right-associative) przecinków, które działają dokładnie jak ampersandy
zaprezentowane wcześniej. Tak więc biorąc pod uwagę regułę:
f(X) :-g(X), h(X), i(X), j(X).
zapytanie:
?clause(f(abc),Body).
Utworzy instancję „Body”
g(abc), h(abc), i(abc), j(abc)
która odpowiada:
g(abc), (h(abc), (i(abc), j(abc)))
Aby wykonać to zadanie postąpimy w ten sam sposób jak zrobiliśmy to w przypadku ampersandów.
Możemy
zdefniować interpret ( który pobiera funkcję jako argument i wykonuje ją).
Następnie aby użyć „interpret” wystarczy napisać, np.:
?interpret(grandparent(X,Y)). Zamiast
?grandparent(X,Y).
Jest to algorytm Clocksin'a i Mellish'a (1984:177) . Cięcia są „zielone” : zachowują kroki, ale nie
wpływają na
logikę programu. Zauważmy, że nie jest tu używane wywołanie; Każda udana funkcja kończy się
wywołaniem interpret(true).
Dostosowywanie interfejsu użytkownika
Nie tylko silnik inferencji, ale również interfejs najwyższego poziomu Prologa może być
spersonalizowany. W typowym najwyższym poziomie
Prologa, komputer ustala:?a użytkownik odpowiada wpisując zapytanie. Niektóre Prologi pozwalają również
użytkownikowi ustalić fakty i zasady, które są dodane do bazy danych. Plik
TOPLEVEL.PL określa najwyższy poziom, którego dialogi z użytkownikiem wyglądają
następująco:
Type a query:
father(X,cathy).
Solution found:
father(michael,cathy)
Look for another? (Y/N): n
Type a query: father(joe,cathy).
No (more)solutions
Type a query: parent(X,Y).
Solution found:
parent(michael,cathy)
Look for another? (Y/N): y
Solution found:
parent(melody,cathy)
Look for another? (Y/N): y
No (more) solutions
Dostosowywanie interfejsu użytkownika
Jest to zwykły „najwyższy poziom” w Prologu z tym wyjątkiem, że komunikaty są
znacznie bardziej jawne, a odpowiedzi są podawane poprzez wyświetlanie zapytań z
wypełnionymi wartościami zmiennych. Wszystkie zapytania Prologa są dopuszczalne
-nie tylko zapytania o bazę danych, ale również wywołania do wbudowanych
predykatów takich jak „consult”.
Kod określający ten „najwyższy poziom” ma tylko 18 linii. Procedura
top_level jest nieskończoną pętlą repeat-fail, która akceptuje zapytania i przekazuje je do
find_solutions. (Zauważ przy okazji, że nazwa
top_level nie ma specjalnego znaczenia. Ta procedura staje się najwyższym poziomem środowiska
Prologa zaraz po uruchomieniu go, wpisując '?-top_level.')W find_solutions procedura ma dwie
klauzule. Pierwsza klauzula znajduje rozwiązanie, wypisuje je, i pyta użytkownika, czy szukać innych.
Jeśli użytkownik wpisze N lub n (na "nie"), find_solutions wykonuje cięcie, i sterowanie powraca do
top_level. W przeciwnym razie, find_solutions cofa się. Jeśli nie można znaleźć dalszych rozwiązań
i cięcie nie zostało wykonane, sterowanie przechodzi do drugiej klauzuli i wyświetla
się komunikat "Brak (dalszych) rozwiązań".
Jak wyjść z top_level? Po prostu wpisz 'halt.' aby wyjść z Prologa, tak jakbyś używał zwykłego
„najwyższego poziomu”.
Spersonalizowany „najwyższy poziom” może uczynić Prolog znacznie bardziej przyjaznym
dla użytkownika. Przydatnym okazało się przedstawienie Prologa początkującym
programistom poprzez interfejs użytkownika kierowanym za pomocą menu, który
pozwalał im wyświetlać bazę danych, dodawać lub usuwać klauzule, oraz wykonywać
zapytania. Dzięki temu używanie edytora plików jest wyeliminowane. Ponadto
spersonalizowany „najwyższy poziom” może być połączony z wzmocnionym silnikiem
inferencji, po to aby zamienić Prologa w potężny system inżynierii danych.
Wykorzystanie języka PROLOG w praktyce!






Analiza semantyczna sceny wizyjnej (lub jakiegokolwiek innego zestawu bodźców), czyli
wstępne przetwarzanie obrazu np. w C/C++ a później wnioskowanie na obrazie za pomocą
bardziej ludzkich wyrażeń czyli np. "znajdź wszystkie niebieskie koła z dziurą w środku leżące
blisko jasnych kształtów".
"Odwracanie" działania złożonych algorytmów poprzez wykorzystanie tego, że prologowe
predykaty są szeroko pojętymi relacjami a nie funkcjami a poza tym współczesny Prolog to
także programowanie z ograniczeniami i propagowanie ograniczeń zamiast propagacji
wartości, co może znacząco przyspieszyć niektóre zadania obliczeniowe.
Wszędzie tam, gdzie mamy do czynienia ze zbiorami reguł (medycyna, prawo, biologia,
matematyka, sieci semantyczne, systemy eksperckie, przetwarzanie języka naturalnego,
semantic web, OWL/RDF) Prolog ma praktyczne zastosowanie i bywa używany
Prolog bardzo przyjemnie realizuje się w dziedzinach związanych z programowaniem z
ograniczeniami (zarówno w dziedzinie liczb całkowitych jak i rzeczywistych), co jest jego
bardzo dużym plusem
Prolog jest chętnie używany do prototypowania
Rzeczywiście, jest używany także do tworzenia aplikacji webowych (np. SWI-Prolog zachęca
do porzucenia PHP/Apache/MySQL na rzecz siebie samego!)
Niżej podaję też trochę różnych współczesnych pakietów oprogramowania, które używają Prologu:









DAVE™ – Free Development Platform for Code Generation
Realtime Railway Station Control (Siemens AG R&D / Deutsche Bahn AG)
Dopasowywanie kształtu okularów do twarzy w sklepach Paris Miki
TextRazor
InFlow - Software for Social Network Analysis & Organizational Network Analysis
ContactExpress / DealBuilder
GridMind / Mycroft Mind
PROSYN (system ekspercki)
Podobno też w Nokii N900 użyto prologu do zarządzania stanem GUI
Dodatkowo:
Dwa ciekawe projekty wykorzystujące Prologa:


Watson - superkomputer, został zaimplementowany za pomocą Javy, C++ i Prologa.
Clarissa - system sterowany głosem, który został użyty na Międzynarodowej Stacji Kosmicznej
Prolog jest również wykorzystywany w aplikacjach webowych, często pomaga osiągnąć większą
wydajność niż sam SQL. Na pewno też wiele systemów ekspertowych zostało napisanych w
prologu.

Podobne dokumenty