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