Arithmetik in Prolog

Arithmetik in Prolog
Prolog beherrscht die grundlegenden arithmetischen Operationen wie Addition,
Multiplikation, Division usw.
Dazu werden die üblichen Zeichen verwendet. Die arithmetischen Zeichen sind lediglich
Funktoren, sie führen direkt keine arithmetischen Operationen durch, z.B.:
?- X = 3+2.
X = 3+2
yes
?- 3+2 = 5.
no
Ausdrücke wie 3+2 werden als komplexe Terme behandelt, die auch als +(3,2) dargestellt
werden können, und diese matchen nicht mit Zahlen (die einfache Terme sind).
Es gibt einen besonderen Operator, der Prolog dazu anweist, die Rechnungen durchzuführen.
Das ist der eingebaute zweistellige Operator is/2. Das erste Argument von is ist immer eine
Zahl (ein einfacher Term), nämlich das Ergebnis, das berechnet wird. Das zweite Argument
sind die Ausdrücke, mit denen gerechnet werden soll; das sind in der Regel komplexe Terme,
z.B. 3+2. Beispiele:
?- 5 is 5.
yes
?- 7 is 5+2.
yes
?- 5+2 is 7.
no
?- X is 3+2*5.
X = 13
Alle Variablen, die im zweiten Argument von is enthalten sind, müssen instantiiert sein.
Wenn eine solche Variable nicht instantiiert ist, gibt Prolog eine Fehlermeldung aus:
?- X=3+2, Y is X.
X = 3+2
Y = 5
?- Y is X, X=3+2.
ERROR: Unhandled exception: is/2: Arguments are not sufficiently
instantiated
Oder auch im einfacheren Fall:
?- 3+2*5 is X.
ERROR: Unhandled exception: is/2: Arguments are not sufficiently
instantiated
Man kann mithilfe von arithmetischen Operatoren auch Prädikate definieren, z.B.
addiere_2_und_multipliziere_durch_4:
addiere_2_und_multipliziere_durch_4(X,Y) :- Y is (X+2)*4.
Prolog beherrscht folgende Operationen mit natürlichen Zahlen (auch mit negativen):
Addition: 8 is 6+2.
Subtraktion: 4 is 6-2.
Multiplikation: 12 is 6*2.
Division: 3 is 6/2.
Wenn das Ergebnis einer Division keine natürliche (d.h. ganze) Zahl ist, wird auf die nächste
ganze Zahl abgerundet: 3 is 7/2.
Die Zahl, die bei einer solchen Division übrig bleibt, wird durch den Funktor mod/2
berechnet:
0 is mod(6,2). (Bei 6/2 bleibt 0 übrig.)
2 is mod(10,4). (Bei 10/4 bleibt 2 übrig, nämlich 4*2 + 2 = 10.)
Es gibt ferner ein Prädikat, das als Abkürzung von X is Y, X is Z dient, nämlich
Y =:= Z. In diesem Fall können beide Argumente sowohl Zahlen als auch komplexe Terme
sein, aber keines der beiden Argumente darf uninstantiierte Variablen enthalten.
Prolog enthält auch weitere vergleichende Operatoren:
X < Y.
X =< Y.
X =:= Y.
X =\= Y.
X >= Y
X>Y
x<y
x≤y
x=y
x≠y
x≥y
x>y
Wie im Fall des Gleichheitsoperators =:= können beide Argumente Zahlen wie auch
komplexe Terme sein, aber sie dürfen keine uninstantiierten Variablen enthalten.
Verarbeitung von Listen und Arithmetik
Zählen dient für unsere Zwecke vor allem zu Verarbeitung von (rekursiven) Datenstrukturen,
insbesondere von Listen.
Ein relativ einfaches Beispiel: die Berechnung der Länge (d.h. der Kardinalität) von Listen.
Basisfall: Die Länge der leeren Liste ist 0.
Rekursive Regel: Wenn die Länge des Schwanzes einer Liste x ist, dann ist die Länge der
Liste x+1.
laenge([],0).
laenge([_|T],N) :- laenge(T,X), N is X+1.
?- laenge([a,b,c,d,e,[a,b],g],X).
X = 7
Dasselbe kann auch durch sog. Akkumulatoren erreicht werden. In Programmiersprachen
werden Variablen gewöhnlich zum Speichern von vorläufigen Resultaten verwendet. Diesen
Zweck erfüllen in Prolog die Akkumulatoren. Es soll nun folgendes Prädikat definiert werden:
akkLaenge(Liste,Akk,Laenge)
Wir geben beim Aufrufen dieses Prädikats den Wert 0 für Akk an. Das Prädikat soll dann bei
der rekursiven Abarbeitung der Liste schrittweise 1 zu Akk addieren. Wenn es bei der leeren
Liste angekommen ist, enthält Akk die Länge der Liste, die dann an die Variable Laenge
weitergegeben werden kann.
akkLaenge([_|T],A,L) :akkLaenge([],A,A).
Aneu is A+1, akkLaenge(T,Aneu,L).
Was ist der Unterschied zwischen diesen Prädikaten? Deklarativ gesehen sind sie äquivalent
und sie liefern das gleiche Ergebnis. Prozedural arbeiten sie jedoch unterschiedlich. Bei
laenge ist das rekursive Prädikat das erste Konjunkt des Rumpfes der Regel, deshalb muss die
ganze Liste zuerst abgearbeitet und dann beim zweiten Konjunkt (der Addition) schrittweise
quasi wiederaufgebaut werden. Bei akkLaenge wird die Liste nur einmal abgebaut und
parallel dazu der Wert des zweiten Arguments jeweils um 1 erhöht. Am Ende der Liste wird
der Wert des Akkumulators einfach mit dem dritten Argument gematcht, dadurch erhalten wir
die Länge der Liste.
Zweites Beispiel für einen Akkumulator: Wir definieren ein dreistelliges Prädikat, dessen
erstes Argument eine Liste von Zahlen ist, und dessen drittes Argument die höchste Zahl aus
dieser Liste ist. Das zweite Argument ist ein Akkumulator.
Das Prädikat arbeitet die Liste von links nach rechts ab und merkt sich die bislang gefundene
höchste Zahl im Akkumulator, die es beim Erreichen der leeren Liste als Endergebnis mit
dem dritten Argument matcht.
accMax([K|S],A,Max) :K > A,
accMax(S,K,Max).
accMax([K|S],A,Max) :K =< A,
accMax(S,A,Max).
accMax([],A,A).
Die erste rekursive Regel sagt: wenn der Kopf der Liste eine Zahl ist, die größer ist als die
Zahl im Akkumulator, dann soll diese Zahl in den Akkumulator eingetragen werden.
Die zweite sagt: wenn der Kopf der Liste kleiner ist als die Zahl im Akkumulator, bleibt
letzterer unverändert.
Es stellt sich schließlich die Frage, mit welcher Zahl der Akkumulator in accMax initialisiert
werden soll. Es bietet sich an, die erste Zahl der Liste als Startwert zu nehmen. Wir definieren
zu diesem Zweck ein zweites, zweistelliges Prädikat, wo wir lediglich die Liste selbst und die
Länge als Argumente angeben. Den Akkumulator setzt dieses Prädikat automatisch:
max(Liste,Max) :Liste = [K|_],
accMax(Liste,K,Max).