Modelleren en programmeren

advertisement
Modelleren en programmeren
Week 9: werken met incomplete datastructuren
1.
Incomplete datastructuren
Een krachtige programmeertechniek is het gebruik van incomplete datastructuren: datastructuren die variabelen bevatten.
Operaties op incomplete datastructuren kunnen zo optimaal de ingebouwde
unificatie uitbuiten.
In deze cursus komen verschillende voorbeelden aan de orde:
I getallen: verschilgetallen in successor notatie
I lijsten: verschillijsten
I bomen: incomplete bomen
I ...
2.
Verschilgetallen
Stel dat we een natuurlijk getal n voorstellen als het verschil tussen twee getallen. Zo zou je 2 kunnen voorstellen als 2 − 0, 3 − 1, 4 − 2, . . .
Klein =
s(s(0))
Groot
− Verschil
s(s(0))
0
s(s(s(0)))
s(0)
s(s(s(s(0))))
s(s(0))
..
..
.
.
s(s(X))
X
← incompleet!
Het verschil s(s(X)) - X vat al deze gevallen samen.
Zero De incomplete datastructuur voor zero wordt X - X: het verschil tussen
een willekeurig getal X en datzelfde getal.
3.
Optellen is een feit
Werken met incomplete termen maakt het mogelijk om recursie te vervangen
door unificatie.
Bekijk de recursieve definitie voor optellen van successorgetallen.
add(0,A,A).
add(s(A),B,s(C)) :- add(A,B,C).
Als we de getallen die add/3 optelt weergeven als verschilparen wordt optellen
een feit. In de definitie voor plusDif/6 vormen de argumenten (1,2), (3,4) en
(5,6) telkens een verschilpaar:
plusDif(A,ARest,B,BRest,Som,SomRest) :ARest=B, BRest=SomRest, A=Som.
De unificaties kunnen meteen in de kop van de regel ingevuld:
plusDif(A,ARest,Arest,BRest,A,BRest).
4.
Werken met verschilgetallen
Het predicaat eval/2 zet een getal in decimale notatie om in zijn interpretatie
als successorgetal.
eval(0,0).
eval(N,s(X)) :- N>0,
N1 is N-1,
eval(N1,X).
?- eval(3,S).
S = s(s(s(0)))
Vergelijk: eval/3 zet een getal in decimale notatie om in een verschilpaar.
eval(0,X,X).
eval(N,s(X),Y) :- N>0,
N1 is N-1
eval(N1,X,Y).
?- eval(3,S,Rest).
?- eval(3,S,0).
S = s(s(s(Rest)))
S = s(s(s(0)))
Voorbeeld: lijst sommeren
We sommeren een lijst van (decimale) natuurlijke getallen en geven het resultaat als een successorgetal. Naieve accumulator versie met expliciet add/3:
somlist(Lijst,Som) :- somlist(Lijst,0,Som). % wrapper
somlist([],D,D).
somlist([H|T],D,D1) :eval(H,Hs), % vertaal H in successorgetal
add(Hs,D,D0), % tel op bij accumulator
somlist(T,D0,D1).
Vergelijk
Verschilparen: sommeren zonder optellen ,
sumlist(Lijst,Som) :- sumlist(Lijst,Som,0). % wrapper
sumlist([],D,D).
sumlist([H|T],D,D1) :eval(H,D,D0), % vertaal H in een verschilpaar
sumlist(T,D0,D1).
sumlist( [3,1,2], s(s(s(s(s(s(D2 )))))), D2 )
s
s
s
D0
I 3: s(s(s(D0 )))
I 1: s(D1 )
= D0
I 2: s(s(D2 ))
= D1
3+1+2: s (s (s (s (s (s (D2 ))))))
s
D1
s
s
D2
5.
Verschillijsten
De techniek van incomplete datastructuren werkt ook voor lijsten.
I Stel dat we een lijst representeren als het verschil van twee lijsten. Bijvoorbeeld: de lijst [a,b] wordt [a,b]-[], of [a,b,c]-[c] of . . . .
I Het verschilpaar [a,b|Rest]-Rest is unificeerbaar met al deze gevallen.
Lege lijst: verschilpaar X-X.
I Met deze incomplete datastructuur kan concatenatie opnieuw aan unificatie worden overgelaten: appendDif(A,B,B,C,A,C).
Expliciete aanroep van appendDif/6 (zoals plusDif/6 hierboven) kan je wegcompileren als je met verschilstructuren werkt en als de waarden van de eerste
twee argumenten bekend zijn op het moment van aanroep.
Concatenatie als unificatie van verschillijsten
•
•
•
1
2
3
R
•
•
4
5
S
Concatenatie als unificatie van verschillijsten
•
•
•
1
2
3
R
•
•
4
5
S
Concatenatie als unificatie van verschillijsten
•
•
•
1
2
3
R
•
•
4
5
S
I Links: [ 1,2,3 | R ], rest: R
I Rechts: [ 4,5 | S ], rest: S
I Samen: R = [ 4,5 | S ] dus [ 1,2,3,4,5 | S ], rest: S
Voorbeeld: quicksort
Het bekende quicksort algoritme werkt als volgt.
I Kies een willekeurig element van de te sorteren lijst.
I Verdeel de rest in elementen kleiner dan het gekozen element, en elementen groter of gelijk aan het gekozen element.
I Sorteer die lijst van kleinere elementen, en de lijst van grotere elementen
m.b.v. het quicksort algoritme.
I Concateneer de gesorteerde lijst van kleinere elementen met het gekozen
element en de gesorteerde lijst van grotere elementen voor het eindresultaat.
quicksort/2: naief
In de Prolog versie hieronder kiezen we de kop van de te sorteren lijst als vergelijkingselement. split(Key,List,Smaller,EqOrBigger) laten we aan de verbeelding
over.
quicksort( [], []).
quicksort( [X|Tail], Sorted) :split( X, Tail, Small, Big),
quicksort( Small, SortedSmall),
quicksort( Big, SortedBig),
append( SortedSmall, [X|SortedBig], Sorted).
% ??$#%$#!!
Op het moment waarop append/3 wordt aangeroepen zijn SortedSmall en SortedBig bekend. We kunnen de aanroep van append/3 dus vermijden door
gebruik te maken van verschillijsten.
quicksort/3: verschillijsten
We kunnen een term Lijst-Verschil gebruiken, of Lijst en Verschil als aparte
argumenten behandelen.
Hier de definitie van quicksort/2 in termen van quicksort/3 met interpretatie
quicksort(Lijst,Gesorteerd,Verschil).
quicksort( [], L, L). % L-L is lege lijst
quicksort( [X|Tail], L, L1) :split( X, Tail, Small, Big),
quicksort( Small, L, [X|L0]),
quicksort( Big, L0, L1).
De wrapper kan er dan zo uitzien:
quicksort(List,Sorted) :- quicksort(List,Sorted,[]).
6.
Binaire bomen
We kunnen binaire bomen representeren met een predicaat t/3.
We schrijven t(Links,Knoop,Rechts) voor een boom met
I Knoop als wortel, en
I Links en Rechts als linker en rechter dochters, waarbij die dochters zelf
binaire bomen zijn.
De lege boom representeren we met de atomaire term []. Een blad van een
binaire boom is dus een structuur t([],Knoop,[]): een knoop met lege linker en
rechter dochter.
7.
Voorbeeld
Een boom: t(t([],b,[]),a,t(t([],d,[]),c,[]))
Een plaatje:
a
c
b
d
8.
Woordenboekbomen
Een binaire woordenboekboom is een binaire boom met een orderingsrelatie op
de knopen.
Een niet-lege boom t(Links,Knoop,Rechts) is geordend
I als alle knopen in Links kleiner zijn dan Knoop en
I alle knopen in Rechts groter, en
I als die beide deelbomen zelf ook geordend zijn.
Een binaire boom die aan die voorwaarden voldoet noemen we een (binaire)
woordenboekboom.
Vraag Is de binaire boom hierboven een woordenboekboom? Zo niet, maak
er dan een woordenboekboom van.
9.
Zoeken in een woordenboekboom
Een element opzoeken in een woordenboekboom is efficiënter dan in een lijst, als
de woordenboekboom tenminste welgebalanceerd is. Vergelijk in/2 hieronder
met member/2.
in(X,t(_,X,_)).
% gevonden!
in(X,t(Links,Knoop,_) ) :Knoop @> X,
% Knoop groter dan X
in(X,Links).
% zoek in de linkerdeelboom
in(X,t(_,Knoop,Rechts) ) :X @> Knoop,
% X groter dan Knoop
in(X,Rechts).
% zoek in de rechterdeelboom
10.
Incomplete woordenboekbomen
Een incomplete datastructuur voor woordenboekbomen krijg je als je de representatie [] voor de lege boom door een variabele vervangt.
De variabele subtermen zijn plaatsen waar een incomplete boom door unificatie
kan groeien.
Zo kan je het programma in/2 hierboven niet alleen gebruiken om te kijken of
een element in een woordenboekboom zit, maar ook om een woordenboekboom
te bouwen.
Probeer dit uit met de query hieronder. Hoeveel voorkomens van b komen er
in de woordenboekboom? Waarom?
?-in(p,D),in(b,D),in(r,D),in(a,D),in(b,D).
11.
dic2list/2
We willen nu de geordende lijst van knopen uit een woordenboekboom extraheren. Een naı̈eve versie zou er zo kunnen uitzien:
dic2list([],[]).
dic2list(t(Links,Knoop,Rechts),Lijst):dic2list(Links,L),
dic2list(Rechts,R),
append(L,[Knoop|R],Lijst).
Laten we kijken hoe dit programma omgeschreven kan worden naar een programma dat van verschillijsten gebruik maakt: dic2dlist/3.
12.
dic2dlist/3
We schrijven dic2dlist(Dic,Lijst,Rest) als het woordenboek Dic omgezet kan
worden in de verschillijst Lijst-Rest. Grensgeval:
dic2dlist([],L,L). % L-L is de verschillijst voor de lege lijst.
Nu het recursieve geval, eerst met een aanroep van appendDiff.
dic2dlist(t(Links,Knoop,Rechts),Lijst,Rest):dic2dlist(Links,L,RestL),
dic2dlist(Rechts,R,RestR),
appendDiff(L,RestL,[Knoop|R],RestR,Lijst,Rest).
appendDiff(A,B,B,C,A,C).
13.
appendDiff wegwerken
dic2dlist(t(Links,Knoop,Rechts),Lijst,Rest):dic2dlist(Links,L,RestL),
dic2dlist(Rechts,R,RestR),
appendDiff(L,RestL,[Knoop|R],RestR,Lijst,Rest).
appendDiff(A,B,B,C,A,C).
De aanroep van appendDiff/3 kan je wegcompileren. Vul de unificerende substituties waardoor appendDiff slaagt direct in in de definitie voor dic2dlist/3.
Het resultaat:
dic2dlist(t(Links,Knoop,Rechts),Lijst,Rest):dic2dlist(Links,Lijst,[Knoop|RestL]),
dic2dlist(Rechts,RestL,Rest).
14.
De DCG notatie
I programma’s met verschillijst concatenatie volgen een vast patroon (links)
I regels met --> (i.p.v. :-) vullen predicaten automatisch aan met verschillijst argumenten
p(. . . ) −−>
..
.
p(. . . , Dstart , Dend ) :..
.
q(. . . , Dstart , D0 ),
..
.
r(. . . , D0 , D1 ),
..
.
kan je schrijven als
s(. . . , D1 , Dend ),
..
..
q(. . . ),
..
.
r(. . . ),
..
.
s(. . . ),
..
..
I { Goal } voor een predicaat dat niet aangevuld moet worden
I lijsten rechts van --> worden omgezet in verschillijsten
Illustraties
list2dlist/3
beeld:
We zetten een lijst om in zijn verschillijst representatie. Bijvoor-
?- list2dlist( [a,b,c],
[a,b,c|Rest], Rest). % true
?- list2dlist( [a,b,c|[]], [a,b,c|Rest], Rest). % is hetzelfde
list2dlist([],L,L).
list2dlist([H|T],[H|R],S) :- list2dlist(T,R,S).
I vervang het eind van de invoer (nl. []) door een paar variabelen L,L
I hoe kom je bij het eind van de invoer? lijst recursie
DCG voor list2dlist/3
De code kan ook geschreven zoals hieronder:
list2dlist([]) --> [].
list2dlist([H|T]) --> [H],list2dlist(T).
Meer illustraties
rev/3
Een lijst omkeren met een accumulator doe je zoals hieronder.
rev([],L,L).
rev([H|T],A,R) :- rev(T,[H|A],R).
Met het 2de en 3de argument omgedraaid wordt dit een verschillijst programma.
drev([],L,L).
drev([H|T],R,A) :- drev(T,R,[H|A]).
Vergelijk de wrappers:
rev(Lijst,Rev) :- rev(Lijst,[],Rev).
drev(Lijst,Rev) :- drev(Lijst,Rev,[]).
DCG voor drev/3
Nu met impliciete verschillijst argumenten.
drev([]) --> [].
drev([H|T]) --> drev(T),[H].
Een voorbeeld met { . . . }
quicksort/3
De verschillijst code voor het sorteeralgoritme.
quicksort( [], L, L).
quicksort( [X|Tail], L, L1) :split( X, Tail, Small, Big),
quicksort( Small, L, [X|L0]),
quicksort( Big, L0, L1).
DCG voor quicksort/3
argumenten.
De aanroep van split/4 moet niet aangevuld met extra
quicksort([]) --> [].
quicksort([X|Tail]) -->
{ split(X,Tail,Small,Big) },
quicksort(Small),
[X],
quicksort(Big).
15.
DCG: Definite Clause Grammars
De DCG --> notatie is bijzonder handig voor het formuleren van grammatica’s
voor (formele of natuurlijke) talen.
Gebalanceerde haakjes Lees 0 als haakje openen en 1 als haakje sluiten. Het
predicaat s/3 staat voor een rijtje van gebalanceerde haakjes.
s --> [0], s, [1], s.
s --> [].
Binaire woorden
Je kan ’of’ gebruiken voor alternatieven
b --> [] ; [0], b ; [1], b.
Vergelijk de aanroep hieronder met je code voor word/3.
?- length(L,3),b(L,[]),write(L),tab(1),fail.
[0,0,0] [0,0,1] [0,1,0] [0,1,1] [1,0,0] [1,0,1] [1,1,0] [1,1,1]
ARTIFICIAL INTELLIGENCE
231
Een classic:
Artificial Intelligence 13 (1980), 231–278
Definite Clause Grammars for Language
Analysis--A Survey of the Formalism and a
Comparison with Augmented Transition
Networks
Fernando C. N. Pereira and David H. D. Warren
Department of Artificial Intelligence, University of Edinburgh
Recommended by Daniel G. Bobrow and Richard Burton
ABSTRACT
A clear andpowerfulformalism for describing languages, both natural and artificial, follows fiom a
method for expressing grammars in logic due to Colmerauer and Kowalski. This formalism, which
is a natural extension of context-free grammars, we call "definite clause grammars" (DCGs).
A DCG provides not only a description of a language, but also an effective means for analysing
strings o f that language, since the DCG, as it stands, is an executable program o f the programming
language Prolog. Using a standard Prolog compiler, the DCG can be compiled into efficient code,
making it feasible to implement practical language analysers directly as DCGs.
This paper compares DCGs with the successful and widely used augmented transition network
(ATN) formalism, and indicates how ATNs can be translated into DCGs. It is argued that DCGs
can be at least as efficient as ATNs, whilst the DCG formalism is clearer, more concise and in
practice more powerful
1. Introduction
The aims of this paper are:
(1) to give an introduction to "definite clause grammars" (DCGs)--a. formalism,
originally described by Colmerauer (1975), in which grammars are expressed as
Download