Material för KTH-kursen
Logikprogrammering, del av DD1351
Thomas Sjöland sjoland@kth.se SCS, Software and Computer Systems
EECS - School of Electrical Engineering and Computer Science KTH, The Royal Institute of Technology
Efter godkänd kurs ska studenten kunna:
specificera allmänna egenskaper hos matematisk-datalogiska strukturer och bevisa dessa med hjälp av naturlig deduktion i satslogik och predikatlogik,
specificera induktiva definitioner hos datastrukturer och bevisa dessa med strukturell induktion, specificera och bevisa systemegenskaper med hjälp av temporal logik,
specificera och bevisa programegenskaper med hjälp av Hoarelogik,
tillämpa metoder för automatisk deduktion och utföra enkla bevis med modellprövning,
tillämpa och förklara grundläggande begrepp inom logikprogrammering: unifiering, backtracking, snitt, negering och olika programmeringstekniker som t.ex. generate- test
i syfte att
behärska de bevistekniker som behövs i kommande kurser i utbildningen.
För högre betyg ska studenten dessutom kunna:
argumentera för korrektheten hos en viss bevisteknik: sundhet och fullständighet, argumentera för bevisteknikers lämplighet till automatisk deduktion: avgörbarhet.
Lärandemål DD1351
Logik som programmeringsspråk - Prolog Likhet för termer - Unifiering
Rekursion, backtracking
DD1351 F1 12/9
Logikprogrammering 1 Thomas Sjöland, KTH
(från Dilian Gurov, KTH för DD1361, HT17)
DD1351
Logik för Dataloger HT18
• Logisk versus procedurell läsning
• Kontrollflöde: Unifiering, Backtracking, Snitt
• Induktiva datatyper och rekursion
• Inbyggda datatyper: listor
• Datatyper med Prolog-termer: träd
• Programmeringtekniker med Prolog
• Generera-och-testa
Delkursinnehåll
Den deklarativa aspekten:
• Problemdomänbeskrivning med relationer
• Fakta och regler
• Antagandet om en sluten värld, Prolog-negation
• Induktiva datatyper som termer
• Inbyggda och implicita i Prolog
• Strukturell induktion: en princip att garantera well-definedness
Den procedurella aspekten: Kontrollflödet
• Unifiering
• Backtrackning
• Typisk programstruktur: generera-och-testa
• Snitt
Lärandemål
• Se kurswebbsidorna: Före kursstart, Kursmaterial
• Kursbok: Learn Prolog Now!
P. Blackburn, J. Bos, och K. Striegnitz
Free online version: www.learnprolognow.org
• SWI-Prolog manual
• Föreläsningsanteckningar
• Prolog-l: *.pl
Delkurslitteratur och -material
Ämnen
• Deklarativ- och logik-programmering
• Satser: fakta och regler
• Logisk versus procedurell läsning
• Unifiering
• Backtracking
• Rekursion
Läsmaterial
• Boken: kap. 1-3
• Prolog-l: intro.pl
• Föreläsningsanteckningar
Idag
Deklarativ programmering
• Programmet är en beskrivning av problemdomänen
• Exekveringar resulterar från frågor (queries)
Logikprogrammering
• En konkret realisering av deklarativ programmering med hjälp av predikat som beskriver relationer mellan objekt
• Domänen beskrivs som en sammansättning av
predikatlogiska formler i så kallad Horn-klausul form
• Frågor besvaras med en sök-algoritm som kallas resolution
Deklarativ- och logikprogrammering
Om f är en funktion så kan vi skriva:
f (a) = b (a, b) ∈ f f (a, b)
funktion relation predikat
Relationer är dock mer generella: inte varje relation är en funktion.
Exempel: ”Fadern till a är b" kan deklareras:
o som funktion: far (a) = b o som predikat: far (a, b)
Däremot är ”a är dotter till b" en relation men ingen funktion!
Från funktioner till predikat
Relationer kan deklareras explicit, varje tupel för sig själv.
Sådana definitioner kallas för fakta:
far(agnes, petter).
far(per, petter).
far(anton, per).
mor(agnes, annika).
mor(per, annika).
mor(kia, agnes).
mor(anton, agnes).
Satser: fakta
Nu när vi har skapat en relationell databas, vill vi kunna ställa
frågor (queries) till den.
?- far(agnes, per).
?- far(anton, per).
?- far(monika, per). % Closed world assumption!
Notera att Prolog inte vet vad vi menar med alla symboler: den resonerar helt symboliskt, utan tolkning!
Prolog vet inte om far(a, b) betyder ”fadern till a är b", eller ”a är fader till b", eller t.o.m. att ”himlen har färgen a och det är b stycken moln på den".
Queries
Vi kan också ställa frågor som involverar variabler:
?- far(agnes, X). % Vem är far till agnes?
% Eller, för vilka X är det sant % att fadern till agnes är X?
?- far(X, petter). % Vems fader är petter?
% Flera svar möjliga!
% Fås fram med 'n' eller ";"
?- far(X, Y).
Fler queries
Relationer kan också definieras med regler:
farmor(X, Y) :- far(X, Z), mor(Z, Y).
Läs: farmodern till X är Y
om fadern till X är (någon) Z och modern till Z är Y.
Vi kan observera att:
":-" läses som "om", dvs implikation ”<-" i omvänd riktning
"," läses som "och", dvs konjunktion "^"
variablerna är (implicit) universellt kvantifierade Sådana formler (satser) kallas för Horn-klausuler
Satser: regler
Disjunktiva regler kan ges som separata regler:
foralder(X, Y) :- far(X, Y).
foralder(X, Y) :- mor(X, Y).
Läs:
en förälder till X är Y (OBS: relation, inte funktion!) om fadern till X är Y
eller modern till X är Y.
Följer logiska ekvivalensen:
p v q -> r (p -> r ) ^ (q -> r )
Satser: regler
Predikatlogikens syntax har både
predikatsymboler såsom funktionssymboler, tagna från olika syntaktiska mängder. I Prolog däremot finns det ingen syntaktisk skillnad mellan dem, och båda representeras som atomer. Det är
kontexten som bestämmer hur symbolerna tolkas.
Tex i satsen:
a(b, c(X)).
är a en 2-ställig predikatsymbol, c en 1-ställig funktionssymbol,
b en konstant (dvs en 0-ställig funktionssymbol), och X en variabel.
Prolog-termer
En riktad graf består av en mängd noder och en mängd riktade bågar mellan noderna. Hur kan vi representera grafer i Prolog?
Representation som Prolog-fakta:
• node(a). deklarerar en nod
• edge(a, b). deklarerar en båge
Varför inte bara edge-deklarationer?
Representationen kan vara svår att arbeta med, tex att skicka som argument till ett predikat.
Problemdomänbeskrivning: Grafer
Representation som Prolog-term:
• noder är atomer a, b, c, . . .
• bågar är termer edge(a, b)
• grafer är termer graph(Nodes, Edges) där
Nodes är en lista av noder, och Edges är en lista av bågar
Problemdomänbeskrivning: Grafer
Två termer kan unifieras om det finns en substitution (med termer) för variablerna i termerna, så att båda termerna blir syntaktiskt identiska.
Exempel:
• X och agnes unifierar med substitutionen X = agnes
• far(X, petter) och far(agnes, petter) unifierar med substitutionen X = agnes
• far(agnes, Y) och far(agnes, petter) unifierar med substitutionen Y = petter
• far(X, petter) och far(agnes, Y) unifierar med substitutionen X = agnes, Y = petter
• Kan far(X, petter) och far(agnes, X) unifiera?
Term-unifiering
Logisk läsning
som en predikatlogisk formel, deklarativt
Procedurell läsning
proceduren som Prolog följer för att hitta ett bevis
Prolog läser fakta och regler uppifrån och ned, och klausuler från vänster till höger
Prolog försöker instansiera variablerna med termer så att målet blir sant: unifiering
om detta misslyckas med ett faktum eller en regel, går Prolog till föregående klausul inom regelns kropp om en sådan finns, och annars till nästa faktum eller regel i programmet: backtracking
Logisk versus procedurell läsning
Hur Prolog exekverar
En fråga?- p(Args).
Bevisas med resolution
- leta efter alla klausuler som definierar p - välj den första, spara de kvarvarande som
alternativ
- matcha argumenten Args med termerna i
klausulens huvud, skapa nödvändiga variabel- bindningar med unifiering
- om unifieringen misslyckas, prova nästa alternativa klausul
- bevisa annars målen i kroppen från vänster till
höger; om detta misslyckas, prova alternativklausul - om alla bevis är klara, presentera bindningarna,
annars resultatet ”no” eller ”fail”.
Mål: farmor(anton, X).
Skapar regelinstans:
farmor(X1, Y1) :- far(X1, Z1), mor(Z1, Y1).
unifierar X1=anton, Y1=X Delmål: far(anton, Z1).
unifierar Z1=per
Delmål: mor(per, X).
unifierar X=annika Svar: X=annika
Kontrollflödet i Prolog: Exempel 1
Mål: foralder(kia, X).
Skapar regelinstans: foralder(X1, Y1) :- far(X1, Y1).
unifierar X1=kia, Y1=X
Delmål: far(kia, X).
Kan inte unifieras. Backtrackar till nästa regel.
Skapar regelinstans: foralder(X2, Y2) :- mor(X2, Y2).
unifierar X2=kia, Y2=X
Delmål: mor(kia, X).
unifierar X=agnes
Svar: X=agnes
Kontrollflödet i Prolog: Exempel 2
Regler kan vara rekursiva, dvs referera till sig själv:
forfader(X, Y) :- foralder(X, Y).
forfader(X, Y) :- foralder(X, Z), forfader(Z, Y).
Betrakta frågan:
?- forfader(kia, X).
Vad blir kontrollflödet till första svaret?
Och till andra svaret? (';' leder till backtracking!)
Vad skulle hända om vi vänder om ordningen på de två reglerna?
Och på de två konjunkterna i andra regeln?
Rekursion
För att få en bättre förståelse av
kontrollflödet i Prolog och trace-funktionen, läs också om lådmodellen: se handouts.
Kontrollflödet i Prolog: Lådmodellen
Induktiva datatyper: Listor (inbyggt)
DD1351 F2 17/9
Logikprogrammering 2 Thomas Sjöland, KTH
(från Dilian Gurov, KTH för DD1361, HT17)
DD1351
Logik för Dataloger HT18
Induktiva datatyper: Listor (inbyggt)
• Listor
• Strukturell induktion över listor
• Kontrollflöde
• Generera och testa
• Strängar
Läsmaterial
• Boken: kap. 4, 6
• Prolog-l: list.pl
• Handouts: Föreläsningsanteckningar
Idag
Listorna utgör en oändlig mängd av Prolog-termer.
Induktiv definition:
en lista är antingen tom [], eller en konstruktion [H | T]
av ett element H ("huvud") och en lista T ("svans").
Listor
Definition i Backus-Naur Form (BNF):
<Lst> ::= [] | [<El>|<Lst>]
där <El> är vilken som helst Prolog-term.
Därmed matchar varje lista l antingen []
eller [H | T] , och vi kan använda detta för att ta isär (destruera) listor för att definiera predikat över listor med strukturell
induktion.
Listor
Notationskonvention:
Vi skriver [a, b, c]
istället för
[a | [b | [c | []]]],
detta gör också Prologs "pretty-print".
OBS även konvention! ’.’(H,T)= [H|T]
Listor
För induktivt definierade datatyper kan vi använda oss av strukturell induktion för att definiera predikat över dem.
Konkret, för listor:
• för tomma listan [], definiera predikatet icke-rekursivt;
• för sammansatta listan [H | T] , definiera predikatet med användning av samma predikat, men bara över svansen T.
Om man följer principen garanterar man att predikatet blir
"väldefinierat" över alla listor.
Läs också texten: "The Principle of Structural Induction" !
Strukturell induktion över listor
is_list([]).
is_list([ _ | T]) :- is_list(T).
first_element([ E | _],E).
last_element([E],E).
last_element([ _ | T],E) :- last_element(T,E),
% OBS syntax: [A] = ’.’(A,[]) = [A|[]]
Predikat över listor
Längden på en lista L är antalet element N i listan.
Definition med strukturell induktion:
• längden på tomma listan [] är 0;
• längden på sammansatta listan [H | T] är längden på svansen T plus 1.
Låt oss använda detta för att implementera listLength(L, N).
Längden på en lista
I Prolog:
listLength([], 0).
listLength([ _ | T], N) :- listLength(T, NT), N is NT + 1.
Notera att vi använder operatorn "is" istället för "=". Varför?
Notera också hur vi använder mönster-matchning i första argumentet för att åstadkomma datatyp-destruktionen som är
nödvändig för strukturella induktionen.
Kan vi vända på ordningen på de två konjunkterna?
Finns även inbyggd som length(L, N).
listLength(L, N)
Mål: listLength([a, b], N).
skapar en instans av andra regeln:
listLength([A1 | T1], N1) :- listLength(T1, NT1), N1 is NT1 + 1.
unifierar A1=a, T1=[b], N1=N
• Delmål: listLength([b], NT1).
• skapar instans av andra regeln:
• listLength([A2 | T2], N2) :- listLength(T2, NT2), N2 is NT2 + 1.
• unifierar A2=b, T2=[], N2=NT1
• Delmål : listLength([], NT2).
• matchar första regeln, unifierar NT2=0
• Delmål : NT1 is 0 + 1.
• evaluerar 0+1 till 1, unifierar NT1=1
• Delmål : N is 1 + 1.
• evaluerar 1+1 till 2, unifierar N=2
Svar: N=2
Kontrollflödet vid listLength([a, b], N).
Från KS:en HT15:
Fråga: length(L, 2).
- misslyckas med första regeln, därför att 0 och 2 inte kan unifieras;
- skapar instans av andra regeln:
length([A1|T1], N1) :- length(T1, NT1), N1 is NT1+1.
unifierar: L=[A1|T1], N1=2;
-- length(T1, NT1).
--- lyckas med första regeln;
unifierar: T1=[], NT1=0;
-- 2 is 0+1.
--- evaluerar 0+1 till 1;
--- misslyckas, därför att 2 och 1 inte kan unifieras;
--- backtrackar;
….. (forts på nästa bild)
Kontrollflödet vid listLength(L, 2).
Från KS:en HT15:
….
(Forts från föregående bild) -- length(T1, NT1).
--- skapar instans av andra regeln:
length([A2|T2], N2) :- length(T2, NT2), N2 is NT2+1.
unifierar: T1=[A2|T2], N2=NT1;
---- length(T2, NT2).
--- lyckas med första regeln;
unifierar: T2=[], NT2=0;
---- NT1 is 0+1.
--- evaluerar 0+1 till 1;
--- unifierar: NT1=1;
-- 2 is 1+1.
--- evaluerar 1+1 till 2;
--- lyckas, därför att 2 kan unifieras med 2.
Svar: L=[A1|[A2|[]]] som presenteras som L=[A1, A2].
Kontrollflödet vid listLength(L, 2). (forts)
Medlemstest som ska vara sant om och bara om X finns i listan L.
in(H, [H | _]).
in(X, [_ | T]) :- in(X, T).
Strukturella induktionen är över listan L (dvs andra argumentet).
in(X, []) är alltid falskt, därför ingen regel för tom lista!
Finns även inbyggd som member(X, L).
Medlemskap i en lista: in(X, L)
• Mål: in(2, L), in(1, L).
• Delmål: in(2, L).
• skapar instans av första regeln:
in(H1, [H1 | A1]).
unifierar H1=2, L=[2 | A1]
• Delmål : in(1, [2 | A1]).
• skapar instans av första regeln:
in(H2, [H2 | A2]).
misslyckas med unifieringen (varför?)
• skapar instans av andra regeln:
in(X1, [A3 | T1]) :- in(X1, T1).
unifierar X1=1, A3=2, T1=A1
• Delmål : in(1, A1).
• skapar instans av första regeln:
in(H3, [H3 | A4]).
unifierar H3=1, A1=[1 | A4]
• Svar: L =[2, 1 | A4] Finns det fler svar?
Kontrollflödet vid in(2, L), in(1, L).
Ska vara sant om Z är konkateneringen av listan X med listan Y.
concatenate([], Y, Y).
concatenate([HX | TX], Y, [HX | TZ]) :- concatenate(TX, Y, TZ).
Strukturella induktionen är över listan X (dvs första argumentet).
Finns även inbyggd som append(X, Y, Z).
Listkonkatenering: concatenate(X, Y, Z)
Listor
Konkatenering av två listor
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append([a,b], [c,d], X). - sammansättning av två listor
. .
a b
[] . .
c d
[]
. .
a b
. .
c d
[]
Listor
“differens” mellan två listor
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, [c,d], [a,b,c,d]).
- finner “differensen” mellan listor
. .
a b
[] . .
c d
[]
. .
a b
. .
c d
[]
Listor
Att dela upp en lista i två delar (1)
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, Ys, [a,b,c,d]).
. .
a b
[] . .
c d
[]
. .
a b
. .
c d
[]
Listor
Att dela upp en lista i två delar (2)
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, Ys, [a,b,c,d]).
. .
a b
[] . .
c d
[]
. .
a b
. .
c d
[]
Listor
Att dela upp en lista i två delar (3)
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, Ys, [a,b,c,d]).
. .
a b
[] . .
c d
[]
. .
a b
. .
c d
[]
Listor
Att dela upp en lista i två delar (4)
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, Ys, [a,b,c,d]).
. .
a b
. .
c d
[]
. .
a b
. .
c d
[]
[]
Listor
Att dela upp en lista i två delar (5)
append([], Xs, Xs).
append([X|Xs], Y, [X|Zs]) :- append(Xs, Y, Zs).
?- append(Xs, Ys, [a,b,c,d]).
. .
a b
. .
c d
[]
. .
a b
. .
c d
[]
[]
Listor
Att vända på en lista
reverse([], []).
reverse([H|T],R) :- reverse(T,S), append(S,[H],R).
?- reverse([a,b,c,d],R). - ger R=[d,c,b,a]
. .
a b
. .
c d
[]
. .
d c
. .
b a
[]
Ska vara sant om listan NL är listan L med elementet X tillagt i slutet.
appendEl(X, [], [X]).
appendEl(X, [H | T], [H | Y]) :- appendEl(X, T, Y).
Strukturella induktionen är över listan L (dvs andra argumentet).
Lägg till ett element: appendEl(X, L, NL)
Ska vara sant om Y är omvända listan X.
rev([], []).
rev([H | T], Y) :- rev(T, RT),
appendEl(H, RT, Y).
Finns även inbyggd som reverse(X, Y).
Listomvändning: rev(X, Y)
Ackumulerarnde parametrar
Reversering av listor
a) naive reverse (använder append I varje rekursionssteg) reverse([], []).
reverse([X|Xs], Ys) :-
reverse(Xs, Zs), append(Zs, [X], Ys).
b) reverse med ackumulator (arg 2 I hjälppredikatet)
reverse(Xs, Ys) :-
reverse(Xs, [], Ys).
reverse([], Acc, Acc).
reverse([X|Xs], Acc, Ys) :-
reverse(Xs, [X|Acc], Ys).
Strängar
• Är symbolsekvenser.
• Representeras i Prolog internt som listor av heltal. Varje tal representerar därmed en
symbol (ASCII-koden).
• Inbyggda predikatet atom_codes(X, Y) är sant när Y är strängen (dvs heltalslistan)
som motsvarar atomen X.
Strängar
Det finns många sätt att sortera listor.
Här ska vi implementera
permutationssortering, som illustration
av hur vi kan använda backtracking som en styrka för att realisera en
programmeringsteknik som kallas för generera och testa.
Listsortering
Denna programmeringsteknik använder sig av
backtracking för att successivt generera en möjlig lösning, testa om den uppfyller villkoren för att vara en korrekt
lösning, sedan backtracka och generera en möjlig lösning till, testa den, osv.
Generella strukturen ser ut så här:
problem(Problem, Solution) :-
generate(Problem, Solution), test(Solution).
För effektivitet behöver man ofta lägga in test i generatorn.
Generera och testa
Vi utgår från den matematiska definitionen av sortering: att sortera en lista kan definieras som att skapa (dvs beräkna) en sorterad permutation av ursprungliga listan:
permSort(X, Y) :-
permutation(X, Y), \\ generera permutation sorted(Y). \\ testa om sorterad
Programmet använder (inbyggda) predikatet
permutation(X, Y) för att generera en permutation av listan X, som sedan testas med predikatet sorted(Y) huruvida den är sorterad eller inte.
Listsortering: permSort(+X, ?Y)
Ska vara sant om Y är en permutation av X.
Strukturella induktionen är på första listan X.
Vi utgår från följande logiska karakterisering av permutation:
Y är en permutation av X om huvudet på X (E) finns i Y, och om E tagits bort från Y, den resulterade listan är en permutation av svansen på X.
permutation([], []).
permutation([E | X], Y) :- permutation(X, Y1),
append(Y2, Y3, Y1), % används för att dela upp Y1 append(Y2, [E | Y3], Y).
Del 1: permutation(+X, ?Y)
%Definieras här bara för listor av tal!
sorted([]).
sorted([X]).
sorted([X, Y | L]) :-
X =< Y,
sorted([Y | L]).
Del 2: sorted(X)
permutation(Xs, [Z|Zs]) :- select(Z, Xs, Ys), permutation(Ys, Zs).
permutation([], []).
select(X, [X|Xs], Xs).
select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).
Mer elegant permute
Induktiva datatyper (ej inbyggda)
DD1351 F3 18/9
Logikprogrammering 3 Thomas Sjöland, KTH
(från Dilian Gurov, KTH för DD1361, HT17)
DD1351
Logik för Dataloger HT18
Induktiva datatyper: Träd (inte inbyggt)
Binära träd utan data
Binära träd med data
Problemdomänbeskrivning
Läsmaterial
Prolog-l: tree.pl
Handouts: Föreläsningsanteckningar
Idag
Binära träd utan data utgör en oändlig mängd av Prolog-termer:
Ett binärt träd utan data är antingen ett löv, leaf, eller ett sammansatt träd, branch(t1, t2) , som består av ett vänster delträd t1 och ett höger delträd t2.
Induktiv definition (BNF)
<Tree> ::= leaf | branch(<Tree>, <Tree>) där leaf och branch kallas för konstruktorer.
Därmed matchar varje binärt träd utan data t antingen leaf eller branch(TL, TR).
Exempel på (stängda) träd-termer: leaf, branch(leaf, leaf), branch(leaf, branch(leaf, leaf)).
Träd-termer kan också innehålla variabler: branch(X, leaf).
Binära träd utan data
För att definiera ett predikat över binära träd utan data:
för löven leaf, definiera predikatet utan rekursion;
för sammansatta träden branch(TL, TR), definiera predikatet med användning av samma predikat över delträden TL och TR.
Då blir predikatet väldefinierat för alla binära träd utan data.
Strukturell induktion
Höjden på ett träd t är längden (antalet bågar) av längsta stigen från roten till något löv.
Definition med strukturell induktion?
höjden på ett löv leaf är 0;
höjden på ett sammansatt träd branch(TL, TR) är maximum av höjderna på delträden TL och TR plus 1.
Höjden på ett träd
max(X, Y, X) :- Y<X.
max(X, Y, Y) :- X=<Y.
height(leaf, 0).
height(branch(TL, TR), N) :- height(TL, NL),
height(TR, NR), max(NL, NR, M), N is M+1.
Höjden på ett träd: height(T, N)
Ett träd är komplett om det har alla sina löv på samma höjd.
Definition med strukturell induktion?
complete(leaf).
complete(branch(TL, TR)) :- complete(TL),
complete(TR), height(TL, N), height(TR, N).
Kompletta träd: complete(T)
Ett binärt träd med data är antingen ett löv leaf(d) med en data-term d, eller ett sammansatt träd branch(d, t1, t2) med en data-term d, ett vänster delträd t1, och ett höger delträd t2.
Induktiv definition (BNF)
<Tree> ::= leaf(<Data>) |
branch (<Data>, <Tree>, <Tree> )
Därmed matchar varje binärt träd med data t antingen leaf(D) eller branch(D, TL, TR).
Exempel:
branch(-3,branch(4,leaf(8),leaf(-2)),leaf(7))
Binära träd med data
Som member(X, L), fast för träd med data.
Definition med strukturell induktion?
lookup(D, leaf(D)).
lookup(D, branch(D, _, _)).
lookup(D, branch(_, TL, _)) :- lookup(D, TL).
lookup(D, branch(_, _, TR)) :- lookup(D, TR).
Medlemskap i ett träd: lookup(D, T)
Beräknar summan av alla tal i trädet (antar att allt data är tal).
Definition med strukturell induktion?
treesum(leaf(N), N).
treesum(branch(N, TL, TR), N1) :- treesum(TL, NL),
treesum(TR, NR), N1 is NL+NR+N.
Summering i ett träd: treesum(T, N)
Operationer
Att lägga till ett element.
Att ta bort ett element.
Problematiskt för binära träd!
Ofullständiga binära träd (utan data)
idé: tillåt också tomma träd
BNF-definition:
<Tree> ::= nil | leaf | branch(<Tree>, <Tree> )
exempel: branch(leaf, branch(nil, leaf))
Strukturella operationer på träd
Ett sorterat binärt träd
Att hitta och lägga till ett värde i ett ordnat binärt träd)
%lookup(+,?,?)
lookup(Key, tree(Key,Value, Left, Right), Value):- !.
lookup(Key, tree(Key1, Value1, Left, Right), Value) :- Key < Key1, lookup(Key, Left, Value).
lookup(Key, tree(Key1, Value1, Left, Right), Value) :-
Key > Key1, lookup(Key, Right, Value).
Exempel på användning:
?- lookup(1, D, fifi),lookup(2, D, mumu),lookup(1,D, X).
Svar:
D=tree(1, fifi, _C, tree(2, mumu, _B, _A)), X=fifi.
Datatypen hierarkiska mjukvarusystem kan definieras induktivt med två regler: ett mjukvarusystem är antingen en funktion (som i språket C), eller en modul bestående av mjukvarusystem.
1. Föreslå ett sätt att representera mjukvarusystem som Prolog- termer, där funktionerna representeras som atomer med samma namn som funktionen, t.ex. main, min, max.
2. Ge två exempel på termer som representerar mjukvarusystem med flera nästlade nivåer.
3. För din representation av datatypen mjukvarusystem, skriv ett predikat funcsMVS(S, FL) som är sant om och bara om FL är en lista som innehaller namnen pa alla funktioner i det hierarkiska mjukvarusystemet S (med möjlig upprepning).
Problemdomänbeskrivning:
Hierarkiska mjukvarusystem
1. Sammansättningar går enklast att representera som listor.
En möjlig BNF-grammatik vore:
<MVS> ::= func(<Name>) | mod(<MVSList>) <MVSList> ::= [] | [<MVS>|<MVSList>]
2. Exempel-termer:
func(foo)
mod([func(main),
mod([func(min), func(max), func(avg)])])
Hierarkiska mjukvarusystem: Prolog-termer
3. Om vi följer strukturella induktionsprincipen för
ömsesidigt rekursiva datatyper (se texten "The Principle of Structural Induction"), så får vi:
funcsMVS(func(N), [N]).
funcsMVS(mod(SL), FL) :-
funcsMVSList(SL, FL).
funcsMVSList([], []).
funcsMVSList([S|SL], FL) :- funcsMVS(S, FL1),
funcsMVSList(SL, FL2), append(FL1, FL2, FL).
Hierarkiska mjukvarusystem: funcsMVS(S, FL)
Cut, negation
Generera-testa i Prolog
DD1351 F4 24/9
Logikprogrammering 4 Thomas Sjöland, KTH
(från Dilian Gurov, KTH för DD1361, HT17)
DD1351
Logik för Dataloger HT18
Prolog-specika konstruktioner
Negation, snitt
Aritmetik, I/O
Kontrollpredikat, metapredikat
Generera och testa Läsmaterial
Boken: kap. 5, 9, 10
Prolog-l: misc.pl
Handouts: Föreläsningsanteckningar
Idag
Prolog utgår ifrån closed world assumption:
allt som Prolog inte lyckas bevisa betraktas som falskt!
Negation i Prolog betraktas som bevis-teoretisk negation, och inte som logisk negation.
snygg(kia).
ej_snygg(X) :- \+ snygg(X).
Vad blir svaren på dessa queries?
?- ej_snygg(nisse).
?- ej_snygg(kia).
?- ej_snygg(X).
Negation i Prolog
Vi implementerade medlemstestet för listor så här:
in(H, [H | _]).
in(X, [_ | T]) :- in(X, T).
Vad blir svaret till:
?- in(X, [1, 2]), \+ in(X, [1,3]).
?- in(X, [1, 2]), \+ in(X, Y).
Beskriv kontrollflödet (variant på uppgift från omtentan 2016-03-15).
disjoint(X, Y) :- \+ (in(Z, X), in(Z, Y)).
Negation i Prolog
Snitt skär bort backtrackingen för predikatet i vilkets kropp den förekommer. Kan användas för att förbättra prestandan genom att undvika onödig sökning..
Exempel:
in(H, [H | _]).
in(X, [_ | T]) :- in(X, T).
Hur många svar får vi på frågan:
?- in(2, [1, 2, 3, 1, 2, 3]).
För att eliminera svaren efter det första:
inCut(H, [H | _]) :- !.
inCut(X, [_ | T]) :- inCut(X, T).
Snitt (eng: cut)
i(1).
i(2).
j(1).
j(2).
j(3).
s(X, Y) :- q(X, Y).
s(0, 0).
q(X, Y) :- i(X), !, j(Y).
q(4, 4).
Vilka blir svaren pa frågan?
?- s(X, Y).
Exempel från Learn Prolog Now!
Betrakta programmet:
max(X, Y, Z) :- X>Y, Z=X.
max(X, Y, Z) :- Z=Y.
Vad blir svaret på:
?- max(4, 3, 3).
Rättad implementation:
maxCut(X, Y, Z) :- X>Y, !, Z=X.
maxCut(X, Y, Z) :- Z=Y.
När behövs snitt?
Betrakta programmet:
snygg(kia).
isnygg(X) :- snygg(X), !, fail.
isnygg(X).
Vad blir svaren på:
?- isnygg(nisse).
?- isnygg(kia).
Vad åstadkommer predikatet isnygg?
Mera snitt
Likhet = betyder syntaktisk likhet, inte "samma värde"!
Speciella predikatet is utvärderar andra termen och unifierar med första. Obs: andra termen måste vara tillräckligt instansierad!
Dubbelsidig utvärdering och jämförelse: =:=.
Aritmetiska operatorer: +, -, *, /, //, mod, etc.
Olikheter: <, >, =<, >=, etc.
Aritmetik i Prolog
Två nödvändiga predikat:
read(t): läs en term fram till nästa punkt och unifiera den med termen t;
write(t): skriv ut termen t till terminalen.
Vad händer vid frågan:
?- read(X+Y), write(Y-3).
I/O i Prolog
Vi betraktade redan fail, ett predikat som alltid misslyckas.
Predikatet call(X) betraktar termen som X är unierad med som ett predikat.
snygg(kia).
not(X) :- call(X), !, fail.
not(X).
Vad blir svaret på denna fråga?
?- not(snygg(nisse)).