Dieses Buch soll in die Programmiersprache PROLOG und in die mit ihr verbundene 'Logik-programmierung' einfĂŒhren. Die Idee, 'Logik als eine Programmiersprache' zu verwenden, fĂŒhrt zu einer völlig neuen Auffassung vom Programmieren: Das Programm ist nicht eine Folge von Handlungsanweisungen, sondern eine Sammlung von Fakten und Regeln. Das zu lösende Problem wird als eine Anfrage an diese Sammlung formuliert. Im Idealfall fĂŒhrt die logisch korrekte Formulierung der Regeln und der Anfrage zur Lösung des Problems.
Dieser Ansatz ist faszinierend, und das allein schon wĂ€re Grund genug, PROLOG als zweite Programmiersprache fĂŒr die Schule in Betracht zu ziehen. Hinzu kommen aber noch andere Vorteile:
- Die BeschÀftigung mit einer Sprache, die sich völlig von den befehlsorientierten Sprachen wie PASCAL unterscheidet, weitet den Horizont; altbekannte Algorithmen erscheinen in einem neuen Licht und neuartige Probleme können behandelt werden.
- Die wesentlichen Grundelemente von PROLOG bilden eine kleine, ĂŒberschaubare Menge. (Sie zu beherrschen ist allerdings nicht ganz einfach.)
- Der aktive Umgang mit logischen, deklarativen Programmen fördert (so hoffen wir) das logische Denken.
Den letzten Punkt halten wir fĂŒr entscheidend; daher haben wir uns in diesem Buch sehr stark auf den logischen Kern der Sprache â auf 'pures' PROLOG â beschrĂ€nkt (und nehmen eine etwas spartanische 'OberflĂ€che' in Kauf). Es geht uns nicht um die möglichst umfassende Vermittlung der Programmiersprache, sondern um das Bekanntmachen eines neuen, mĂ€chtigen und schönen Programmierkonzeptes. Zum Vertrautwerden mit diesem neuen Konzept bedarf es vieler sinnvoller Beispiele und Aufgaben; wir hoffen, hier genĂŒgend bereitgestellt zu haben.
Das Buch besteht im wesentlichen aus zwei Teilen. Der erste Teil gibt eine EinfĂŒhrung in die Arbeitsweise von PROLOG, das grundlegende Verfahren der Rekursion und den Datentyp der Liste. Der zweite Teil besteht aus elf Vertiefungen; diese sind weitgehend unabhĂ€ngig voneinander und nach steigender KomplexitĂ€t geordnet. FĂŒr einen Kurs schlagen wir vor, zuerst den ersten Teil (evtl. ohne Kapitel 7) und dann mindestens eine Vertiefung durchzuarbeiten. Ein 'Schnupperkurs' von wenigen Stunden (vielleicht schon in Sekundarstufe I) könnte aus Kapitel 1 und 2, dem Anfang von Kapitel 3 und der Vertiefung B, der Datenbasisprogrammierung, bestehen. Bei Bedarf könnte dies noch mit einigen RĂ€tseln aus Vertiefung A gewĂŒrzt werden.
Wir verwenden PROLOG nach dem sogenannten Edinburgh-Standard, der durch das Buch von CLOCKSIN und MELLISH definiert wurde und sich inzwischen weitgehend durchgesetzt hat. In Versionen, die sich nach diesem Standard richten, laufen die Programme des Buches problemlos. FĂŒr einige gĂ€ngige Versionen sind gezielte Hinweise im Anhang gegeben. Dort findet man auch Hinweise auf die Bezugsquellen.
Wir bedanken uns bei dem Herausgeber, Herrn StD Dietrich Pohlmann, fĂŒr zahlreiche Anregungen und VerbesserungsvorschlĂ€ge und beim Verlag Ferd. DĂŒmmler fĂŒr die freundliche Betreuung. Wir danken weiterhin Ingrid Hafenbrak fĂŒr die Illustrationen, Frau Sylvia Platz und Herrn Martin Kammerer fĂŒr die Hilfe bei der Gestaltung des Manuskriptes und Herrn Dr. Klaus Scheler fĂŒr das sorgfĂ€ltige Korrekturlesen. SchlieĂlich gilt unser Dank den Mannheimer SchĂŒlern und Lehrern vom Elisabeth Gymnasium und vom Gymnasium Feudenheim, die uns ermöglichten, unsere Ideen in der Schulpraxis zu erproben.
Es freut uns, dass nach recht kurzer Zeit eine Neuauflage erforderlich wurde. Dabei wurden nur einige Druckfehler und Unstimmigkeiten berichtigt; die beiden Auflagen sind also uneingeschrÀnkt nebeneinander im Unterricht einsetzbar. Heidelberg und Weingarten
Hartmut Göhner, Bernd Hafenbrak
Wir bedanken uns bei den beiden Autoren, die dem Landesinstitut fĂŒr Schule und Ausbildung Mecklenburg-Vorpommern (L.I.S.A.) die Texte und Programme zur VerfĂŒgung stellten, damit wir das â leider vergriffene â Arbeitsbuch PROLOG in den Landesbildungsserver einstellen konnten. Schwerin
Gabriele Lehmann
--{{0}}--
Einen kleinen BlumenstrauĂ zu beginn.
--{{1}}--
Dieses Buch ist leider im original nicht im Vierfarbendruck erschienen aber wir können den Willkommensstrauà auch kurz mit einfachen Worten wie:
{{1}}
- Die Rose ist rot.
- Die Tulpe ist gelb.
- Die Nelke ist weiĂ.
- Das VergiĂmeinnicht ist blau.
- Das Veilchen ist blau.
--{{2}}--
Solche Fakten (Tatsachen) wie die Zusammensetzung eines StrauĂes können in PROLOG wie folgt abgebildet werden:
{{2}}
rot(rose).
gelb(tulpe).
weiss(nelke).
blau(vergissmeinnicht).
blau(veilchen).
--{{2}}--
Die PrĂ€dikate (Eigenschaften) rot, gelb, weiss und blau treffen auf gewisse Konstanten wie zum Beispiel rose zu, dies schreiben wir in der obigen Form. Sowohl PrĂ€dikate als auch Konstanten werden mit kleinem Anfangsbuchstaben geschrieben, deutsche Sonderzeichen vermeiden wir. Jedes Faktum wird mit einem Punkt und dem DrĂŒcken der RETURN-Taste abgeschlossen.
--{{0}}--
Die hier genuzte PROLOG-IDE besteht immer aus zwei Teilen, einer Eingabe fĂŒr das Programm. Wenn du hier auf AusfĂŒhren klickst, so wird dieses Programm geladen. Da dieses Programm jedoch einen syntaktischen Fehler hat, erhĂ€ltst du zunĂ€chst eine Fehlermeldung, die auf ein Syntax-Problem hinweist.
--{{1}}--
Du kannst den Code auch direkt editieren und den fehlenden Punkt am Ende der letzten Zeile einfĂŒgen. Wenn du dann wieder auf AusfĂŒhren klickst, so wird dir angezeigt, dass deine Datenbasis erfolgreich geladen werden konnte. Und falls du dir spĂ€ter mal wegen einer Ănderung nicht sicher bist, so kannst du auch jede beliebige Version wieder herstellen, indem du einfach auf die Pfeil-Buttons neben der aktuellen Versionnummer klickst.
rot(rose).
gelb(tulpe).
weiss(nelke).
blau(vergissmeinnicht).
blau(veilchen)
@Tau.program(blumenstrauss.pro)
--{{2}}--
Um anfragen an deine Datenbasis zu stellen, benötigst du noch eine zweite Eingabemöglichkeit:
{{2-4}}
rot(rose).
@Tau.query(blumenstrauss.pro)
--{{3}}--
Solche Eingaben werden als Fragen aufgefasst. Umgangsprachlich formuliert heiĂt
das: "Ist die Rose rot?". Als Antwort erscheint true
.
--{{4}}--
Und auf die Frage gelb(veilchen).
erhalten wir false
. Versuch weitere
solcher Fragen einzugeben. Du wirst sehen: Kommt die Frage buchstabengetreu als
Faktum in der Datenbasis vor, so antwortet PROLOG mit true
, andernfalls mit
false
.
{{4}}
gelb(veilchen).
@Tau.query(blumenstrauss.pro)
--{{0}}--
Wir können mit Hilfe von Variablen auch etwas anspruchsvoller fragen: "Was ist
blau?". Gibt es mehrere Lösungen, so wird zunÀchst immer nur eine angeboten. Du
kannst weitere Lösungen anfordern, indem du wiederholt auf AusfĂŒhren klickst.
Gibt es schlieĂlich keine weitere Lösung mehr, so erscheint false.
mit einem
einem abschlieĂenden Punkt.
blau(X).
@Tau.query(blumenstrauss.pro)
--{{1}}--
Variablen werden mit einem groĂen Anfangsbuchstaben geschrieben. Dieselbe Frage können wir auch mit einer anderen (mehr aussagekrĂ€ftigeren) Variablen stellen. Beachte wie sich die Ausgabe verĂ€ndert.
{{1}}
blau(Blume).
@Tau.query(blumenstrauss.pro)
--{{0}}--
Dies ist die Urlaubsplanung fĂŒr die nĂ€chsten Ferien, die umgangssprachliche Formulierung kann ganz einfach in eine PROLOG-Programm ĂŒbersetzt werden.
Umgangsprachlich: Axel fĂ€hrt nach England, Beate fĂ€hrt nach Griechenland und in die TĂŒrkei, Clemens, Elmar und Frederike fahren nach Frankreich, Dagmar fĂ€hrt nach Italien.
faehrt_nach(axel,england).
faehrt_nach(beate,griechenland).
faehrt_nach(beate,tuerkei).
faehrt_nach(clemens,frankreich).
faehrt_nach(dagmar,italien).
faehrt_nach(elmar,frankreich).
faehrt_nach(frederike,frankreich).
@Tau.program(urlaubsplanung.pro)
--{{1}}--
In dieser Datenbasis gibt es nur ein PrÀdikat, das zweistellige PrÀdikat
faehrt_nach
. Laden die obige Datenbasis in PROLOG. Die Frage "Wer fÀhrt nach
England?" heiĂt in PROLOG:
{{1}}
faehrt_nach(X,england).
@Tau.query(urlaubsplanung.pro)
--{{2}}--
Beantworte die folgenden Fragen, indem du sie in PROLOG ĂŒbersetzt und vergleiche deine Anfragen mit den Auflösungen:
-
FĂ€hrt Axel nach Griechenland?
[( )] Ja [(X)] Nein
faehrt_nach(axel, griechenland).
@Tau.query(urlaubsplanung.pro)
-
Wohin fÀhrt Beate?
[[ ]] england [[ ]] frankreich [[X]] griechenland [[ ]] italien [[X]] tuerkei
faehrt_nach(beate, X).
@Tau.query(urlaubsplanung.pro)
-
Wohin fÀhrt Xaver?
[[ ]] england [[ ]] frankreich [[ ]] griechenland [[ ]] italien [[ ]] tuerkei
Xaver fÀhrt nirgends hin, er ist nicht in der Datenbasis enthalten.
faehrt_nach(xaver, Urlaubsziel).
@Tau.query(urlaubsplanung.pro)
-
Wer fÀhrt nach Frankreich?
[[ ]] Axel [[ ]] Beate [[X]] Clemens [[ ]] Dagmar [[X]] Elmar [[X]] Frederike
faehrt_nach(Wer, frankreich).
@Tau.query(urlaubsplanung.pro)
-
Wer fÀhrt wohin?
<script>true;</script>[[!]]
faehrt_nach(Person, Ziel).
@Tau.query(urlaubsplanung.pro)
{{0-1}}
--{{0}}--
Die Vorlieben und Abneigungen am FrĂŒhstĂŒckstisch seien in der folgenden PROLOG-Datenbasis mit dem Namen 'fruehstueck.pro' festgehalten:
mag(papa,muesli).
mag(papa,brot).
mag(mami,kuchen).
mag(mami,brot).
mag(oma,brot).
mag(baby,muesli).
mag(baby,kuchen).
hasst(papa,kuchen).
hasst(mami,muesli).
hasst(oma,muesli).
hasst(oma,kuchen).
hasst(baby,brot).
@Tau.program(fruehstueck.pro)
mag(papa, brot).
@Tau.query(fruehstueck.pro)
--{{1}}--
Bis jetzt jetzt soltest du in der Lage sein, vier Arten von Fragen stellen. Du kannst dich selber testen und PROLOG dazu bringen, diese Fragen zu beantworten.
{{1-2}}
-
Mag Papa Kuchen?
-
Wer haĂt MĂŒsli?
-
Was mag Oma?
-
Wer mag was?
--{{2}}--
FĂŒr die FrĂŒhstĂŒcksplanung sind aber auch zusammengesetzte Fragen wichtig die PrĂ€dikate mit und oder oder verknĂŒpft. Das Zeichen in PROLOG fĂŒr und ist ein Komma, fĂŒr oder schreibt man Semikolon.
--{{3}}--
Damit ergeben sich folgende PROLOG-Fragen:
{{2-4}}
-
Wer haĂt Kuchen und mag MĂŒsli?
{{3}}
hasst(X,kuchen), mag(X,muesli).
@Tau.query(fruehstueck.pro)
-
Wer mag Kuchen und Brot?
{{3}}
mag(X,brot), mag(X,kuchen).
@Tau.query(fruehstueck.pro)
-
Wer mag Brot oder Kuchen?
{{3}}
mag(X,brot); mag(X,kuchen).
@Tau.query(fruehstueck.pro)
--{{4}}--
Teste jetzt dein Wissen und versuch die folgenden Fragen mit PROLOG zu beantworten und vergleiche deine Lösungen mit den Auflösungen.
{{4}}
-
Wer mag Kuchen und MĂŒsli?
[[ ]] Mami [[X]] Baby [[ ]] Papa [[ ]] Omi
mag(X, kuchen), mag(X, muesli).
@Tau.query(fruehstueck.pro)
-
Was mögen sowohl Papa als auch Mami?
[[ ]] Kuchen [[X]] Brot [[ ]] MĂŒsli
mag(papa, X), mag(mami, X).
@Tau.query(fruehstueck.pro)
-
Wer mag Kuchen und haĂt MĂŒsli?
[[ ]] Baby [[X]] Mami [[ ]] Papa [[ ]] Omi
mag(X, kuchen), hasst(X, muesli).
@Tau.query(fruehstueck.pro)
--{{0}}--
Die folgenden beiden Abschnitte sollen dazu dienen, das bereits gelernte zu wiederholen und zu festigen, bevor wir lernen wie man komplexere Sachverhalte durch die Nutzung von Regeln abbildet.
Stellen Sie die Gegebenheiten des WillkommensstrauĂes von Aufgabe 1 mit Hilfe eines zweistelligen PrĂ€dikates farbe dar.
rot(rose).
gelb(tulpe).
weiss(nelke).
blau(vergissmeinnicht).
blau(veilchen).
@Tau.program(blumenstrauss2.pro)
@Tau.query(blumenstrauss2.pro)
Welchen Vorteil hat diese zweistellige Darstellung?
[[!]]
Vorteil: Man kann auch Fragen stellen wie: "Welche Farbe hat die Rose?"
blume(rot, rose).
blume(gelb, tulpe).
blume(weiss, nelke).
...
?- blume(rot, X).
--{{0}}--
Ăbersetz die folgenden SĂ€tze in eine PROLOG-Datenbasis.
{{0-1}}
- Peter liebt Susi.
- Hans liebt Susi und Sabine.
- Sabine liebt Peter und haĂt Hans.
- Susi liebt Peter und Felix.
- Susi haĂt Sabine.
- Peter haĂt Felix.
- Felix liebt sich selbst.
% gib hier die Beziehungen ein
--{{1}}--
Versuch die folgenden Anfragen selbst zu lösen, bevor du sie mit den Auflösungen vergleichst:
{{1}}
-
Wen liebt Sabine?
[[!]]
liebt(sabine, X).
-
Wer liebt Sabine?
[[!]]
liebt(X, sabine).
-
Wer liebt wen?
[[!]]
liebt(Wer, Wen).
-
Wer liebt jemanden, der ihn auch liebt?
[[!]]
liebt(X, Y), liebt(Y, X).
-
Wessen Liebe wird mit HaĂ vergolten?
[[!]]
liebt(X, Y), hasst(Y, X).
--{{0}}--
Der folgende Stammbaum von Donald und Daisy lĂ€Ăt eine gewisse Systematik bei der Namensgebung erkennen, die den Ăberblick erleichtert:
â Adam ââââââ
âŁââââ â Baldur ââââââ
â Adele âââââ âŁââââ â Casanova
â Alfred ââââ âŁââââ â Clemens âââââ
âŁââââ â Barbara âââââ â
â Alwine ââââ âŁââââ â Donald
â Anton âââââ âŁââââ â Daisy
âŁââââ â Berta âââââââ â
â Anna ââââââ âŁââââ â Cleopatra âââ
â Arthur ââââ âŁââââ â Cosima
âŁââââ â Bernd âââââââ
âŁââââ â Boris
â Adriane âââ
--{{1}}--
Es gibt verschiedene Möglichkeiten, die Informationen dieses Stammbaumes in einer Datenbasis festzuhalten. Wir wĂ€hlen dazu die PrĂ€dikate maennl, weibl, verheiratet und elter. Die Datenbasis wird schon recht groĂ. In Ihrer Datei erscheint sie einspaltig, da jedes Faktum mit Punkt und RETURN abgeschlossen wird.
{{1}}
maennl(adam).
maennl(alfred).
maennl(anton).
maennl(arthur).
maennl(baldur).
maennl(bernd).
maennl(boris).
maennl(casanova).
maennl(clemens).
maennl(donald).
weibl(adele).
weibl(alwine).
weibl(anna).
weibl(ariadne).
weibl(barbara).
weibl(berta).
weibl(cleopatra).
weibl(cosima).
weibl(daisy).
verheiratet(adam,adele).
verheiratet(adele,adam).
verheiratet(alfred,alwine).
verheiratet(alwine,alfred).
verheiratet(anton,anna).
verheiratet(anna,anton).
verheiratet(arthur,ariadne).
verheiratet(ariadne,arthur).
verheiratet(baldur,barbara).
verheiratet(barbara,baldur).
verheiratet(bernd,berta).
verheiratet(berta,bernd).
verheiratet(clemens,cleopatra).
verheiratet(cleopatra,clemens).
/* elter(X,Y) heiĂt: Y ist Elternteil von X */
elter(baldur,adam).
elter(baldur,adele).
elter(barbara,alfred).
elter(barbara,alwine).
elter(bernd,anton).
elter(bernd,anna).
elter(berta,arthur).
elter(berta,ariadne).
elter(boris,arthur).
elter(boris,ariadne).
elter(casanova,baldur).
elter(casanova,barbara).
elter(clemens,baldur).
elter(clemens,barbara).
elter(cleopatra,bernd).
elter(cleopatra,berta).
elter(cosima,bernd).
elter(cosima,berta).
elter(donald,clemens).
elter(donald,cleopatra).
elter(daisy,clemens).
elter(daisy,cleopatra).
@Tau.program(stammbaum.pro)
% Anfragen hier eingeben.
@Tau.query(stammbaum.pro)
--{{2}}--
Beachte, wie sich die Symmetrie des PrÀdikats verheiratet
in der Datenbasis
ausdrĂŒckt. Das PrĂ€dikat elter
bedarf einer ErlÀuterung. Hierzu wurde ein
noch einen Kommentar eingefĂŒgt:
{{2-3}}
Kommentar: /* elter(X,Y) heiĂt: Y ist Elternteil von X */
--{{2}}--
Alles was zwischen den Kommentarzeichen /*
und */
steht, wird von PROLOG
ignoriert. FĂŒr den Benutzer ist im obigen Fall ein solcher Kommentar notwendig,
da die Reihenfolge von X und Y von uns willkĂŒrlich (in Anlehnung an
Gepflogenheiten der Mathematiker) festgelegt wurde.
--{{3}}--
Lade das Programm und versuche die folgenden Fragen zu stellen und zu
beantworten. Nutze bei deinen Anfragen X
als freie Variable!
{{3-4}}
-
Wer sind die Eltern von Daisy?
[[elter(daisy, X).]] @Tau.check(stammbaum.pro,`setof(X, @input, [clemens, cleopatra])`)
-
Mit wem ist Baldur verheiratet?
[[verheiratet(baldur, X).]] @Tau.check(stammbaum.pro,`setof(X, @input, [barbara])`)
-
Wie heiĂen die Kinder von Arthur?
[[elter(X, arthur).]] @Tau.check(stammbaum.pro,`setof(X, @input, [barbara])`)
--{{4}}--
Wenn wir nun die Mutter von Cosima suchen, mĂŒssen wir eine zusammengesetzte Frage stellen: Welchen weiblichen Elternteil hat Cosima?. In PROLOG lautet das wie folgt. Beide Fragen sind logisch gleichwertig und erzielen dieselbe Antwort. Auf Unterschiede bei der Abarbeitung der beiden Anfragen wollen wir erst in Kapitel 3 eingehen.
{{4-5}}
Wer ist die Mutter von Cosima?
elter(cosima,X), weibl(X).
@Tau.query(stammbaum.pro)
oder ...
weibl(X), elter(cosima,X).
@Tau.query(stammbaum.pro)
{{5}}
Versuche selbst als kleine FingerĂŒbung auf jeweils zwei verschiedene Arten nach dem Vater von Daisy, nach den Söhnen von Barbara und nach den Töchtern von Anton!
--{{6}}--
Suchen wir die GroĂeltern von Donald, dann erreichen wir dies durch die folgende Anfrage. In Worten: "Gesucht sind E und G, so dass E Elternteil von Donald und G Elternteil von E ist.
{{6-7}}
elter(donald,E), elter(E,G).
@Tau.query(stammbaum.pro)
--{{7}}--
Versuche selbst die folgenden Fragen zu lösen!
{{7-8}}
-
Wer ist die GroĂmĂŒtter von Clemens?
[[!]]
weibl(Oma), elter(E, Oma), elter(clemens, E).
@Tau.query(stammbaum.pro)
-
Wer sind die UrgroĂeltern von Daisy?
[[!]]
elter(G, E), elter(E, G), elter(daisy, E).
@Tau.query(stammbaum.pro)
-
Wie heiĂt die Schwiegermutter von Bernd?
[[!]]
verheiratet(bernd, F), elter(F, S), weibl(S).
@Tau.query(stammbaum.pro)
--{{8}}--
Eine besondere Schwierigkeit tritt auf, wenn wir den Bruder von Clemens suchen. Der Bruder ist das Kind der beiden Eltern von Clemens, das ergibt die folgende Anfrage. Da sich diese Frage sich nicht mehr in einer Zeile unterbringen lĂ€Ăt, können wir auch mehrere Zeilen fĂŒr eine Anfrage nutzen. Erst durch den Punkt wird die Anfrage abgeschlossen.
{{8-9}}
elter(clemens, V), maennl(V), elter(clemens, M), weibl(M),
elter(X, V), elter(X, M), maennl(X).
@Tau.query(stammbaum.pro)
--{{9}}--
Diese Anfrage nach den BrĂŒdern von Clemens ist jedoch noch fehlerhaft. AuĂer der
richtigen Lösung Casanova erscheint auch Clemens selbst als Antwort. Wir
benötigen hier ein PrĂ€dikat fĂŒr die Ungleichheit, dies wird in PROLOG
geschrieben als \=
. Unsere Frage nach dem Bruder von Clemens lautet damit wie
folgt:
{{9}}
elter(clemens, V), maennl(V), elter(clemens, M), weibl(M),
elter(X, V), elter(X, M), maennl(X), X \= clemens.
@Tau.query(stammbaum.pro)
--{{10}}--
Versuch diese Anfrage selbst verÀndern um auch nach den Schwestern von Cosima zu suchen.
--{{0}}--
Im vorigen Beispiel waren einige Grundbegriffe wie Elternteil, mĂ€nnlich, weiblich durch die Datenbasis erklĂ€rt, andere Begriffe wie Vater, Schwiegermutter oder Bruder mussten wir bei Anfragen in diese Grundbegriffe ĂŒbersetzen. Dieses umstĂ€ndliche Verfahren können wir vereinfachen, indem wir zu den Fakten unserer Datenbasis noch Regeln hinzufĂŒgen. Im Beispiel wĂ€ren das die Regeln
mutter(X,Y) :- elter(X,Y), weibl(Y).
vater(X,Y) :- elter(X,Y), maennl(Y).
kind(X,Y) :- elter(Y,X).
schwiegermutter(X,Y) :- verheiratet(X,Z), mutter(Z,Y).
bruder(X,Y) :- vater(X,V), mutter(X,M),
vater(Y,V), mutter(Y,M), maennl(Y), Y\=X.
maennl(adam).
maennl(alfred).
maennl(anton).
maennl(arthur).
maennl(baldur).
maennl(bernd).
maennl(boris).
maennl(casanova).
maennl(clemens).
maennl(donald).
weibl(adele).
weibl(alwine).
weibl(anna).
weibl(ariadne).
weibl(barbara).
weibl(berta).
weibl(cleopatra).
weibl(cosima).
weibl(daisy).
verheiratet(adam,adele).
verheiratet(adele,adam).
verheiratet(alfred,alwine).
verheiratet(alwine,alfred).
verheiratet(anton,anna).
verheiratet(anna,anton).
verheiratet(arthur,ariadne).
verheiratet(ariadne,arthur).
verheiratet(baldur,barbara).
verheiratet(barbara,baldur).
verheiratet(bernd,berta).
verheiratet(berta,bernd).
verheiratet(clemens,cleopatra).
verheiratet(cleopatra,clemens).
/* elter(X,Y) heiĂt: Y ist Elternteil von X */
elter(baldur,adam).
elter(baldur,adele).
elter(barbara,alfred).
elter(barbara,alwine).
elter(bernd,anton).
elter(bernd,anna).
elter(berta,arthur).
elter(berta,ariadne).
elter(boris,arthur).
elter(boris,ariadne).
elter(casanova,baldur).
elter(casanova,barbara).
elter(clemens,baldur).
elter(clemens,barbara).
elter(cleopatra,bernd).
elter(cleopatra,berta).
elter(cosima,bernd).
elter(cosima,berta).
elter(donald,clemens).
elter(donald,cleopatra).
elter(daisy,clemens).
elter(daisy,cleopatra).
@Tau.program(stammbaum+regeln.pro,@input)
mutter(X,Y).
@Tau.query(stammbaum+regeln.pro)
{{1-5}} Neues Zeichen: :-
==> falls
--{{1}}--
Dabei wird das Zeichen :-
gelesen als 'falls' oder 'wenn'. Der Regelteil vor
dem Zeichen :-
heiĂt Kopf der Regel, der Rest heiĂt Rumpf der Regel.
--{{1}}--
Umgangssprachlich lesen wir die Regel fĂŒr mutter als: Y ist Mutter von X, wenn Y Elternteil von X ist und Y weiblich ist.
--{{1}}--
Die Regel fĂŒr schwiegermutter heiĂt: Y ist Schwiegermutter von X, falls eine Person Z mit X verheiratet ist und Y Mutter von Z ist.
--{{2}}--
Manche PrÀdikate werden durch mehrere Regeln beschrieben:
{{2-3}}
schwager(X,Y) :- verheiratet(X,Z), bruder(Z,Y).
schwager(X,Y) :- schwester(X,Z), verheiratet(Z,Y).
In Worten: Y ist Schwager von X, falls X mit einer Person Z verheiratet ist und Y Bruder von Z ist oder falls X eine Schwester Z hat, die mit Y verheiratet ist.
--{{3}}--
Sowohl Fakten als auch Regeln bezeichnen wir als Klauseln. Die Gesamtheit aller Klauseln bildet ein PROLOG-Programm. Dieses wird mit Hilfe des Editors als Datei angelegt. Mit consult wird das Programm geladen.
--{{4}}--
Lies die Regel fĂŒr das PrĂ€dikat bruder
umgangssprachlich. ErgÀnze die das
Programm stammbaum2.pro
um Regeln fĂŒr die folgenden Verwandtschaftsbeziehungen
und schreibe vor jedes PrÀdikat einen Kommentar zur ErlÀuterung:
{{4-5}}
-
Sohn
[[!]]
/* sohn(X, Y) heiĂt: Y ist Kind von X und Y ist mĂ€nnlich */ sohn(X, Y) :- kind(X, Y), maennl(Y).
-
Tochter
[[!]]
/* tochter(X,Y) heiĂt: Y ist Kind von X und Y ist weiblich */ tochter(X, Y) :- kind(X, Y), weibl(Y).
-
Schwester
[[!]]
/* schwester(X,Y) heiĂt: Y ist die Schwester von X, wenn beide den gleichen Vater und die gleiche Mutter haben und Y ist weiblich und X und Y nicht die gleiche Person sind. */ schwester(X,Y) :- vater(X,V), mutter(X,M), vater(Y,V), mutter(Y,M), weibl(Y), Y\=X.
-
GroĂeltern
-
FĂŒge Kommentare ein, w. z. B.
/* vater(X,Y) heiĂt: Y ist Vater von X */
--{{5}}--
Lade dein Programm und frage mit Hilfe der neuen PrĂ€dikate nach den GroĂeltern von Donald, dem Bruder von Clemens usw. ĂberprĂŒfen Sie, ob PROLOG die Antworten gibt, die du aufgrund des Stammbaums erwartest.
{{5}}
Frage nach:
- GroĂeltern von Donald,
- dem Bruder von Clemens
- usw.
--{{0}}--
Bis jetzt haben wir Regeln verwendet, um neue PrÀdikate mit Hilfe der schon
bekannten zu definieren. Man kann Regeln auch dazu benutzen, den Geltungsbereich
von schon bekannten PrÀdikaten zu erweitern; zum Beispiel haben wir in der Datei
fruehstueck.pro
die PrÀdikate mag und hasst vorliegen, die Vorlieben und
Abneigungen beim FrĂŒhstĂŒck beschreiben. Nun sei bekannt, dass der Opa dieser
Familie alles mag, was Oma haĂt. Diese Regel lautet dann in PROLOG:
mag(opa,X):- hasst(oma,X).
- Nehmen Sie diese Regel in das PROLOG-Programm auf. Welche Antworten erwarten Sie bei den Fragen
?- mag(opa,X). ?- mag(X,kuchen). ?- mag(opa,muesli). ?- hasst(opa,X).
Zur Gruppe aus der Datei urlaub.pro stöĂt Romeo. Er fĂ€hrt ĂŒberall hin, wo Beate hinfĂ€hrt. Wie lautet diese Regel in PROLOG? ErgĂ€nzen Sie die Datei urlaub.pro.
Der Nibelungen Not:
-
Siegfried liebt Krimhild und mag Gunther.
-
Krimhild liebt Siegfried und haĂt Brunhild.
-
Gunther liebt Brunhild und mag Krimhild und Hagen.
-
Brunhild haĂt Siegfried, Gunther und Krimhild.
-
Hagen haĂt Siegfried und alle, die Siegfried lieben.
-
Brunhild mag alle, die Siegfried hassen.
-
Alberich haĂt alle, mit Ausnahme von sich selbst.
--{{0}}--
Schreibe die obigen Aussagen als PROLOG-Programm in eine Datei nibelungen.pro
.
@Tau(nibelungen.pro,%program,%fragen)
--{{1}}--
Stelle die folgenden Fragen:
{{1-2}}
-
Wer haĂt Siegfried?
-
Wen mag Brunhild?
-
Wer haĂt wen?
-
Wer liebt wen?
{{2}}
Definieren ein PrÀdikat ideales_paar, das auf (X,Y) zutrifft, falls X von Y und Y von X geliebt wird.
[[!]]
/* schwester(X,Y) heiĂt: Y ist die Schwester von X, wenn beide den gleichen
Vater und die gleiche Mutter haben und Y ist weiblich und X und Y nicht die
gleiche Person sind. */
schwester(X,Y) :- vater(X,V), mutter(X,M),
vater(Y,V), mutter(Y,M), weibl(Y), Y\=X.
@Tau.query(nibelungen.pro)
--{{0}}--
Regeln kennen wir auch aus der Grammatik. An einem sehr einfachen Beispiel wollen wir einen Zusammenhang mit PROLOG aufzeigen.
-
Der Hund bellt.
-
Der Hase flieht.
-
Der Fuchs flieht.
-
Der JĂ€ger schieĂt.
--{{1}}--
Diese SÀtze sind alle nach demselben Schema gebildet, das wir als PROLOG-Regel schreiben können:
{{1}}
artikel(der).
nomen(hund).
nomen(hase).
nomen(fuchs).
nomen(jaeger).
verb(bellt).
verb(flieht).
verb(schiesst).
satz(X,Y,Z):- artikel(X), nomen(Y), verb(Z).
@Tau.program(grammatik.pro)
--{{1}}--
Damit haben wir eine kleine Sprache definiert, die ĂŒber einen sehr begrenzten Wortschatz und ĂŒber eine einzige grammatikalische Regel verfĂŒgt und natĂŒrlich nur einen ganz engen Bereich unserer Umgangssprache abdeckt.
--{{2}}--
Verwenden Sie das PrĂ€dikat satz, um zu ĂŒberprĂŒfen, ob drei Worte einen Satz unserer Sprache bilden. Beispiele:
{{2-3}}
satz(der,jaeger,bellt).
@Tau.query(grammatik.pro)
satz(flieht,der,hund).
@Tau.query(grammatik.pro)
--{{3}}--
Verwende das PrÀdikat satz auch, um alle möglichen SÀtze dieser Sprache zu erzeugen (Wieviele verschiedene SÀtze erwartest du):
{{3}}
satz(A,B,C).
@Tau.query(grammatik.pro)
In einer GaststÀtte gibt es
- Vorspeisen: Tomatensuppe, Lauchsuppe, FleischbrĂŒhe mit Backerbsen.
- Hauptgerichte: Sauerbraten mit SpÀtzle, LeberkÀse mit Kartoffeln, Hackbraten mit Reis.
- Nachspeisen: Eis, Obstsalat, Bienenstich.
Ein MenĂŒ besteht aus Vorspeise, Hauptgericht und Nachspeise.
Schreiben Sie ein Programm, das ein dreistelliges PrĂ€dikat menue enthĂ€lt. Dieses PrĂ€dikat soll MenĂŒvorschlĂ€ge ĂŒberprĂŒfen und erzeugen können.
--{{0}}--
Das dargestellte Rechteck besteht aus 4 Gebieten, die mit den drei Farben rot, gelb und blau so eingefÀrbt werden sollen, dass keine gleichfarbigen Gebiete lÀngs einer Linie aneinandergrenzen.
âââââââââââââââłââââââââââââââââââââ
â â â
â 1 â 2 â
â â â
âŁââââââââââââââ»ââââââłââââââââââââââ«
â â â
â 4 â 3 â
â â â
âââââââââââââââââââââ»ââââââââââââââ
--{{1}}--
Wir lassen ein Programm nach den Lösungen suchen. Die Farbe des Gebietes 1
bezeichnen wir mit der Variablen F1
, und so weiter. Dabei bedeutet
einfaerbung(F1, F2, F3, F4)
, dass die Farben F1
, F2
, F3
, F4
eine
erlaubte EinfÀrbung des Rechtecks liefern.
{{1}}
farbe(rot).
farbe(gelb).
farbe(blau).
einfaerbung(F1, F2, F3, F4) :-
farbe(F1), farbe(F2), farbe(F3), farbe(F4),
F1\==F2, F1\==F4, F2\==F3, F2\==F4, F3\==F4.
% todo: einfaerbung2
@Tau.program(vier_farben.pro)
--{{2}}--
Wir bekommen also die Lösungen durch die folgende Anfrage:
{{2}}
einfaerbung(F1, F2, F3, F4).
@Tau.query(vier_farben.pro)
{{3}}
Versuche nun selbst eine Regel einfaerbung2(F1, F2, F3, F4, F5)
fĂŒr das das
folgende Rechteck, bestehend aus 5 Gebieten, zu definieren. Nutze wieder nur
drei Farben und achte darauf, dass keine gleichfarbigen Gebiete
aneinandergrenzen?
ââââââââââââââââââłâââââââââââââââââ
â â â
â 1 ââââââ»âââââ 2 â
â â â â
âŁââââââââââââ« 3 âŁââââââââââââ«
â â â â
â 5 ââââââłâââââ 4 â
â â â
ââââââââââââââââââ»âââââââââââââââââ
ĂberprĂŒfe deine Regel.
[[!]]
setof([A,B,C,D,E], einfaerbung2(A,B,C,D,E),
[[blau, gelb, rot, blau, gelb],
[blau, rot, gelb, blau, rot],
[gelb, blau, rot, gelb, blau],
[gelb, rot, blau, gelb, rot],
[rot, blau, gelb, rot, blau],
[rot, gelb, blau, rot, gelb]]).
Der Körper der Regel einfaerbung2
mĂŒĂte in etwa wie folgt aussehen:
% einfaerbung2(F1,F2,F3,F4,F5) :-
farbe(F1), farbe(F2), farbe(F3), farbe(F4), farbe(F5),
F1\==F2, F1\==F3, F1\==F5,
F2\==F3, F2\==F4,
F3\==F4, F3\==F5,
F4\==F5.
@Tau.query(vier_farben.pro)
--{{0}}--
Der junge Prinz sucht die schöne TÀnzerin der vergangenen Nacht. Auf der Flucht hat sie ihren goldenen Schuh verloren. Mit diesem besucht er nun die Töchter des Landes, um nachzuschauen, bei welcher der Fuà in den Schuh passt.
--{{1}}--
Die Suche wĂ€re weniger mĂŒhsam, wenn die Daten der Untertanen schon auf dem Computer verfĂŒgbar wĂ€ren. Es sei etwa auf dem königlichen Hofcomputer eine PROLOG-Datenbasis abgelegt:
{{1}}
schuhgroesse(adelheid,34).
schuhgroesse(agnes,28).
schuhgroesse(aschenputtel,26).
schuhgroesse(brunhilde,44).
schuhgroesse(kunigunde,28).
schuhgroesse(walburga,38).
@Tau.program(aschenputtel.pro)
{{2-6}}
schuhgroesse(aschenputtel,26).
@Tau.query(aschenputtel.pro)
--{{3}}--
Bei der der obigen Abfrage vergleicht PROLOG vergleicht diese der Reihe nach mit
den Fakten der Datenbasis in aschenputtel.pro
. Beim dritten Faktum erreicht es
eine Deckung, die Anfrage und dieses Faktum matchen (sprich: mÀtschen, vom
englischen to match, zusammenpassen).
--{{4}}--
PROLOG arbeitet hier also genau wie unser Prinz. Die Anfrage (der Schuh) wird
mit einem Faktum (einem FuĂ) verglichen. Passen beide nicht zusammen, so geht
PROLOG zum nÀchsten Faktum; passen sie, wird das dem Benutzer mit true
mitgeteilt. Wurde die ganze Datenbasis durchlaufen, ohne dass ein zur Frage
passendes Faktum gefunden wurde, so gibt PROLOG die Meldung false
aus.
--{{5}}--
Es ist einleuchtend, dass diese schematische Suche von einer Maschine verrichtet werden kann; und du hast auch schon oft beobachtet, dass PROLOG diese Fertigkeit fehlerfrei beherrscht. Als Modell können wir uns vorstellen, dass die Maschine eine Schablone mit der Anfrage ĂŒber die Datenbasis zieht, bis eine Deckung erreicht ist.
{{5-6}} Schablone ==> schuhgroesse(aschenputtel,26).
--{{6}}--
Nehmen wir an, der Prinz mit dem Schuh in der Hand stelle die folgende Anfrage.
{{6-10}}
schuhgroesse(X, 26).
@Tau.query(aschenputtel.pro)
--{{7}}--
Wieder macht sich PROLOG ans Suchen, ob zu dieser Anfrage ein Faktum passt. Hierbei befolgt es den Grundsatz:
{{7-8}} Eine Variable kann mit jeder Konstanten matchen.
--{{8}}--
Die Anfrage matcht mit dem dritten Faktum der Datenbasis, dabei matcht X
mit
aschenputtel
. Das Ergebnis der erfolgreichen Suche wird wie unten dargestellt
zurĂŒckgegeben. Das heiĂt, die Anfrage ist erfĂŒllbar, wenn die Variable X
an
die Konstante aschenputtel
gebunden wird.
{{8-10}}
X = aschenputtel ;
--{{9}}--
Verlangen wir vom System weitere Antworten auf die Frage, so löst PROLOG die
Variable X
von der Konstanten aschenputtel
und setzt die Suche fort. Da es
in der Datenbasis keine weitere Möglichkeit des Matchens findet, gibt es die
Antwort false
aus.
--{{10}}--
Betrachten wir die Abarbeitung einer Frage, die mehrere Antworten zulĂ€Ăt:
{{10}}
schuhgroesse(X, 28).
@Tau.query(aschenputtel.pro)
--{{11}}--
PROLOG vergleicht Faktum fĂŒr Faktum mit der Frage und kann beim zweiten Faktum
matchen, indem es X
mit agnes
belegt. Die Antwort lautet also:
{{11}}
X = agnes ;
--{{11}}--
Fordern wir PROLOG auf, weiter zu suchen, so wird X
wieder von agnes
gelöst
und die Frage mit dem dritten, vierten und fĂŒnften Faktum verglichen. Erst beim
fĂŒnften ist wieder das Matchen möglich:
{{12}}
X = kunigunde ;
--{{12}}--
Lassen wir nochmals weitersuchen, so wird X
wieder von kunigunde
gelöst und
die Frage mit dem sechsten Faktum verglichen. Dort ist Matchen nicht möglich,
und damit ist die Datenbasis erschöpft, also erhalten wir die Antwort false
.
--{{0}}--
Damit verlassen wir das schlichte Aschenputtel und wenden uns einem reichhaltigeren Beispiel zu, dem Stammbaum von Donald und Daisy aus Kapitel 1. Dort hattest du in einer Aufgabe nach dem Vater von Daisy gesucht:
@stammbaum_db(stammbaum2.pro) @Tau.program(stammbaum2.pro)
elter(daisy, X), maennl(X).
@Tau.query(stammbaum2.pro)
--{{1}}--
Das Ziel von PROLOG ist es, die zwei Forderungen an X
zu erfĂŒllen. Dies macht
es, indem es nacheinander zwei Teilziele anstrebt. ZunÀchst versucht es, das
erste Teilziel elter(daisy, X)
zu erreichen und findet auch nach etlichen
Vergleichen eine Möglichkeit zu matchen mit X = clemens
. Die Variable X
ist
damit instantiiert (belegt) mit clemens
. Das zweite Teilziel lautet damit
maennl(clemens)
, diese Forderung kann beim Durchsuchen der Datenbasis
bestÀtigt werden. Erst wenn beide Teilziele erreicht sind, wird die Antwort
ausgegeben:
{{1}}
X = clemens;
false.
--{{2}}--
Fragen wir nochmals nach dem Vater von Daisy, dann versucht PROLOG in dieser
Anfrage das erste Teilziel maennl(X)
zu erreichen, und dies gelingt auch
sofort mit X = adam
. Mit dieser Instantiierung von X
wird das zweite
Teilziel angegangen, also elter(daisy, adam)
. Diese Ergebnisse können leicht
mit dem originalen Stammbaum verglichen werden; PROLOG muss Faktum fĂŒr
Faktum mit der Frage vergleichen und geht die ganze Datenbasis durch, bis es zum
gleichen Ergebnis kommt. Die Instantiierung von X
mit adam
fĂŒhrt also nicht
zum Ziel und wird deshalb rĂŒckgĂ€ngig gemacht. PROLOG gibt die Variable X
wieder frei und versucht maennl(X)
mit einem anderen Faktum zu matchen. Schon
beim nÀchsten Faktum ist dies möglich und ergibt X = alfred
. Aber auch damit
scheitert es am zweiten Teilziel, und so probiert PROLOG nacheinander adam
,
alfred
, anton
usw. aus, bis es schlieĂlich mit X = clemens
beide
Teilforderungen erfĂŒllen kann.
{{2}}
maennl(X), elter(daisy,X).
% 1: maennl(X)
% 1: X = adam
% 2: eltern(daisy, adam) = false
% 2: maennl(X)
% 1: X = alfred
% 2: eltern(daisy, alfred) = false
% .: ...
%
% 9: maennl(X)
% 1: X = clemens
% 2: eltern(daisy, clemens) = true
@Tau.query(stammbaum2.pro)
Die folgende Anfragen ist vom deklarativen (beschreibenden) Standpunkt aus gleichwertig zu der gerade besprochenen; sie sind aber verschieden, wenn man sie unter prozeduralen Gesichtspunkten betrachtet, das heiĂt, ihre Abarbeitung verfolgt. Fragen an das System können vom prozeduralen Standpunkt aus als Anweisungen gesehen werden. Die untere Anfrage können wir deklarativ ĂŒbersetzen mit "Wer ist Elternteil von Daisy und mĂ€nnlich?" oder prozedural mit "Suche ein Elternteil X von Daisy, suche solange, bis du ein mĂ€nnliches X mit dieser Eigenschaft findest".
elter(daisy,X), maennl(X).
Mit einer Anfrage geben wir PROLOG ein Ziel fĂŒr eine Suche. Dieses Ziel besteht meist aus mehreren Teilzielen, die PROLOG nacheinander anstrebt. Ein Teilziel ist erreicht, wenn PROLOG geeignete Variablenbelegungen gefunden hat, welche die Forderung des Teilziels erfĂŒllen. Man sagt dann kurz (und sprachlich unsauber): "Das Teilziel ist erfĂŒllt".
-
Es werde die Frage nach der Mutter von Daisy gestellt:
elter(daisy,X), weibl(X).
[[!]]
jjj
Beschreiben Sie das Vorgehen von PROLOG, insbesondere, mit welchen Konstanten X instantiiert wird. BegrĂŒnden Sie, warum die Anfrage
?- weibl(X), elter(daisy,X).
vom prozeduralen Standpunkt aus ungĂŒnstiger ist. Es gibt eine Möglichkeit, PROLOG bei seiner Arbeit Protokoll fĂŒhren zu lassen, und zwar mit Hilfe des PrĂ€dikats write. Dieses PrĂ€dikat ist vom deklarativen Standpunkt nicht beschreibbar; man kann höchstens die Eigenschaft festhalten, dass es immer wahr ist. Es hat aber den 'Seiteneffekt', dass write(X) die jeweilige Belegung der Variablen X auf den Bildschirm bringt. Durch EinfĂŒgen dieses PrĂ€dikats können wir also Zwischenergebnisse sichtbar machen.
Leider bringt die hier genutzte Prolog-Implementierung keine write
Funktion
mit sich aber mit einem kleinen Trick, können wir eine Àhnlich FunktionalitÀt
ganz einfach nachbauen. Du musst dazu nur, den unten stehenden Code im Anfang
von stambaum2.pro
einfĂŒgen:
:-use_module(library(js)).
write(X) :- apply(alert, [X], X).
- ĂberprĂŒfen Sie Ihre Ăberlegungen von Aufgabe 1 durch EinfĂŒgen von write(X) in die obigen Abfragen: ?- elter(daisy,X), write(X), nl, weibl(X). ?- weibl(X), write(X), nl, elter(daisy,X).
(Das PrĂ€dikat nl bedeutet 'new line' und bewirkt einen Zeilenvorschub.) In Kapitel 2 haben wir mit Regeln gearbeitet. Auch sie wollen wir noch kurz prozedural betrachten. Zur Datenbasis des Stammbaums wurde z. B. die Regel hinzugefĂŒgt
vater(X,Y):- elter(X,Y), maennl(Y).
Die deklarative Lesart dieser Regel kennen wir schon: "Y ist Vater von X, falls Y Elternteil von X und mĂ€nnlich ist." Prozedural gesehen ist diese Regel eine Anweisung fĂŒr den Computer, wie er bei der Suche nach dem Vater vorzugehen hat: "Ist der Vater Y von X gesucht, so suche zunĂ€chst ein Elternteil Y und versuche auch noch die Bedingung zu erfĂŒllen, dass dieses mĂ€nnlich ist." Also wird z. B. die Anfrage
?- vater(daisy,V).
intern ersetzt durch die Frage
?- elter(daisy,V), maennl(V).
Die Abarbeitung dieser Frage haben wir schon oben betrachtet. Manche PrĂ€dikate werden durch mehrere Regeln festgelegt; z. B. brauchen wir fĂŒr den Begriff
'Schwager' zwei Regeln:
/* schwager(X,Y) heiĂt: Y ist Schwager von X */
schwager(X,Y):- verheiratet(X,Z), bruder(Z,Y). schwager(X,Y):- schwester(X,Z), verheiratet(Z,Y). Es werde jetzt die Frage gestellt
?- schwager(cosima,S).
Nach der ersten Regel versucht PROLOG zunĂ€chst die Teilziele verheiratet(cosima,Z) und bruder(Z,S) zu erfĂŒllen. Dies gelingt nicht. Deshalb wird diese Regel verlassen und der Schwager von Cosima wird nach der zweiten Regel gesucht. Dazu versucht PROLOG die beiden Teilziele schwester(cosima,Z) und verheiratet(Z,S) zu erfĂŒllen, was schlieĂlich auch gelingt.
Wie Sie gesehen haben, verfĂŒgt PROLOG bei der Suche nach Lösungen ĂŒber einen recht leistungsfĂ€higen Grundalgorithmus. Entscheidungen, die in eine Sackgasse fĂŒhren, werden wieder rĂŒckgĂ€ngig gemacht und es wird die nĂ€chste Möglichkeit ausprobiert. Ein solches Vorgehen wird von den Informatikern Backtracking (RĂŒcksetzen) genannt. Dieses Backtracking wollen wir zum SchluĂ noch am letzten Beispiel des vorigen Kapitels erlĂ€utern.
Es ging dort um die EinfÀrbung eines Gebietes mit drei Farben und wir hatten folgen- des Programm angegeben:
einfaerbung(F1,F2,F3,F4):- farbe(F1), farbe(F2), farbe(F3), farbe(F4), F1=F2, F1=F4, F2=F3, F2=F4, F3=F4. Wir fragen jetzt nach der Lösung des Problems ?- einfaerbung(F1,F2,F3,F4).
PROLOG will diese Anfrage mit Hilfe der Regel lösen. Dazu versucht es nacheinander die Teilziele auf der rechten Seite zu erfĂŒllen. Die ersten Teilziele farbe(F1), farbe(F2), farbe(F3), farbe(F4) sind schnell erfĂŒllbar, indem alle Variablen F1 bis F4 mit rot belegt werden: F1=rot, F2=rot, F3=rot, F4=rot. Damit sind alle Variablen, die in der obigen Regel vorkommen, belegt; es ist eine vollstĂ€ndige Belegung der Variablenmenge erzeugt. Die nĂ€chsten Teilziele F1=F2, F1=F4,... ĂŒberprĂŒfen nun, ob diese Variablenbelegungen das EinfĂ€rbeproblem lösen. Schon die erste ĂberprĂŒfung F1=F2 scheitert, damit setzt das Backtracking ein. Als erstes wird die letzte Entscheidung (F4=rot) rĂŒckgĂ€ngig gemacht; F4 wird wieder freigegeben und versuchsweise mit gelb bzw. blau belegt. Da dies auch nicht zum Ziel fĂŒhrt, wird dann die vorletzte Entscheidung (F3=rot) aufgehoben; die Variable F3 ist nun frei und es werden die Belegungen probiert
F3=gelb,F3=gelb,F3=gelb,F3=blau,F3=blau,F3=blau,F4=rot F4=gelb F4=blau F4=rot F4=gelb F4=blau
Sicher haben Sie schon bemerkt, dass diese Versuche fĂŒr die ErfĂŒllung des Teilziels F1=F2 alle zwecklos sind; PROLOG kommt durch braves Ausprobieren zum selben SchluĂ und hebt nun auch die drittletzte Entscheidung (F2=rot) auf. Es versucht die nĂ€chste Möglichkeit F2=gelb, F3=rot, F4=rot, und damit sind die ersten fĂŒnf Teilziele der rechten Seite erfĂŒllt. PROLOG wendet sich nun dem sechsten Teilziel zu: F1=F4. Mit der derzeitigen Variablenbelegung ist dieses Teilziel nicht erfĂŒllt, also beginnt wieder das Bachtracking. Die letzte Entscheidung F4=rot wird aufgehoben und die nĂ€chste Möglichkeit, die die Datenbasis anbietet, wird versucht: F4=gelb. Wir haben jetzt also die Belegung F1=rot, F2=gelb, F3=rot, F4=gelb, die die ersten sechs Teilziele erfĂŒllt. Diese Belegung erfĂŒllt allerdings noch nicht die beiden letzten Teilziele F2=F4 und F3=F4. Durch nochmaliges Backtracking landen wir aber schlieĂlich bei der Belegung F1=rot, F2=gelb, F3=rot, F4=blau, die sĂ€mtliche Bedingungen erfĂŒllt.
Wir veranschaulichen uns das Backtracking in einem Diagramm (siehe nÀchste Seite). Es sind 4 Entscheidungen zu treffen, die Belegungen der Variablen F1, F2, F3, F4. Bei jeder Entscheidung gibt es 3 Möglichkeiten, damit gibt es insgesamt 34=81 Möglichkeiten, die in einem Baum (der hier von links nach rechts wÀchst) dargestellt werden können. Hier ist nur ein Teil des Baumes gezeichnet.
Wir durchlaufen den Entscheidungsbaum von links nach rechts, die Entscheidung fĂŒr rot wird symbolisiert durch einen Weg nach schrĂ€g oben, gelb entspricht waagrecht nach rechts, blau wird dargestellt durch schrĂ€g nach unten. Nach vier Entscheidungen landet man ganz rechts bei einer Belegung der vier Variablen. Der RĂŒcknahme einer Entscheidung entspricht das ZurĂŒckgehen nach links bis zum vorigen Knoten, wo dann eine neue Entscheidung getroffen werden kann. Wir waren zunĂ€chst beim Endpunkt 'rrrr' angelangt, durch ĂberprĂŒfen, ZurĂŒckgehen und nochmaliges Versuchen kamen wir zu den ZustĂ€nden 'rrrg' und 'rrrb'; da diese auch nicht die Bedingungen erfĂŒllten, mussten wir noch eine Entscheidungsebene zurĂŒckgehen usw. So können Sie im Baumdiagramm die Entscheidungen und das Backtracking verfolgen, die uns schlieĂlich zum Zustand 'rgrb' gefĂŒhrt haben. Sie sehen, dass PROLOG zwölf Belegungen der vier Variablen ausprobieren musste, bis es fĂŒndig wurde.
. . . ââââ rrrr
. . ââââ r âââââââ rrrg
. . â ââââ rrrb
. . â .
. . â ââââ rrgr
. ââââ r âââââââ g âââââââ rrgg
. â â ââââ rrgb
. â â .
. â â ââââ rrbr
.âââ r ââââ« ââââ b âââââââ rrbg
. â . ââââ rrbb
. â . .
. â . ââââ rgrr
. ââââ g ââââââ r ââââââââ rgrg
. . . ââââ rgrb
. . . .
F.1 F.2 F.3 F.4
Das Programm arbeitet nach dem Prinzip 'Erzeuge und ĂberprĂŒfe' (generate and test). Die ersten vier Teilziele farbe(F1), ...., farbe(F4) erzeugen die Belegung sĂ€mtlicher Variablen, diese Belegungen werden durch die folgenden Teilziele F1=F2, ..., F3=F4 ĂŒberprĂŒft.
-
Der Benutzer erfragt bei obigem Programm weitere Lösungen. ErgÀnzen Sie das obige Baumdiagramm und verfolgen Sie das Backtracking bis zur nÀchsten Lösung.
-
ErgĂ€nzen Sie das Programm durch vier write-Befehle fĂŒr die Variablen F1 bis F4, die nach den ersten vier Teilzielen gesetzt werden. Damit lassen Sie die Belegung der Variablen protokollieren. Vergleichen Sie mit dem Baumdiagramm.
-
Machen Sie das obige Programm schneller, indem Sie die Reihenfolge der Teilziele verĂ€ndern. Das ĂberprĂŒfen sollte nicht erst am SchluĂ, sondern so bald wie möglich geschehen.
Wenn Sie PROLOG bei seiner Suche zuschauen wollen, können Sie das im Trace-Modus tun (siehe Anhang). Das Schöne bei PROLOG ist aber, dass sich der Programmierer um die Organisation der Suche nicht kĂŒmmern muss. FĂŒr viele Probleme reicht daher eine deklarative Beschreibung des Problems aus, die vielleicht durch eine grobe Vorstellung des Suchvorgangs abgerundet wird. Keinesfalls ist es nötig, sich bei jeder Anfrage Gedanken darĂŒber zu machen, welche Instantiierungen und Vergleiche bei der Bearbeitung vorgenommen werden.
Hier sehen Sie ein Bild von Donald, dessen Stammbaum uns schon in den Kapiteln 1 und 2 beschÀftigt hat. Donald hat sich vor dem Bild seines Vaters (Clemens) fotografieren lassen. Als dieses Bild von Clemens vor etwa 25 Jahren aufgenommen wurde, hat sich Clemens vor das Bild seines Vaters (Baldur) gestellt, der sich vor 50 Jahren vor dem Bild seines Vaters (Adam) aufgestellt hatte. Auf diese Weise ist in einem Bild eine ganze Galerie von Vorfahren eingefangen.
Um den Begriff 'Vorfahr' geht es in diesem Abschnitt. Schon einem kleinen Kind können wir diesen Begriff schnell erklÀren:
Vorfahren sind die Eltern, GroĂeltern, UrgroĂeltern, UrurgroĂeltern, UrururgroĂeltern usw.
Wir erklÀren PROLOG zunÀchst diese Begriffe:
grosselter(X,Y):- elter(X,Z), elter(Z,Y).
urgrosselter(X,Y):- elter(X,Z), grosselter(Z,Y).
ururgrosselter(X,Y):- elter(X,Z), urgrosselter(Z,Y).
urururgrosselter(X,Y):- elter(X,Z), ururgrosselter(Z,Y).
Schwierig ist nur die Verallgemeinerung, der Begriff 'Vorfahr', da PROLOG im Gegensatz zu uns mit dem 'usw.' nichts anzufangen weiĂ. Dieses 'usw.' dient uns dazu, die unendlich vielen Möglichkeiten einzufangen. In PROLOG benötigen wir hierfĂŒr die Rekursion. Die rekursive Definition von 'Vorfahr' lautet:
vorfahr(X,Y):- elter(X,Y). vorfahr(X,Y):- elter(X,Z), vorfahr(Z,Y).
Das heiĂt: Vorfahren von X sind die Eltern von X und die Vorfahren der Eltern von X. Der Begriff 'Vorfahr' wird also teilweise durch sich selbst erklĂ€rt. Dass dies trotzdem keine unsinnige, in sich kreisende Definition ist, sieht man am besten, wenn man sie prozedural betrachtet.
Dann sind die obigen Regeln eine Anweisung an PROLOG:
- Wenn du einen Vorfahr von X suchen sollst, so suche zunÀchst die Eltern von X.
- Wenn du noch weiter nach Vorfahren suchen sollst, so suche ein Elternteil Z und von diesem einen Vorfahr.
Dieses Vorgehen wollen wir fĂŒr eine spezielle Anfrage nachvollziehen. Es sei gefragt:
âââââââ
â 7 â
âŁââââââ«
â 6 â
âââââââ âŁââââââ«
â 2 â â 5 â
âŁââââââ« âââââââ âŁââââââ«
â 1 â â 3 â â 4 â
ââ»ââââââ»ââââââ»ââââââ»ââââââ»ââââââ»â
a b c