4: Listen

  • Listen sind sehr mächtige Datenstrukturen in Prolog.
  • Listen sind endliche Sequenzen von Elementen:
% Liste mit atomaren Elementen:
[mia, vincent, jules, mia]
% Liste mit verschiedenen Termen als Elemente: [mia, 2, mother(jules), X, 1.7]
% leere Liste:
[]
% Listenelemente koennen Listen sein:
[mia, [[3,4,paul], mother(jules)], X]
  • Listen können verschachtelt sein (Listen können Listen als Elemente haben)
  • Die Reihenfolge der Elemente ist wichtig [a,b,c] = [b,a,c] (Unterschied zu Mengen).
  • Dasselbe Element kann mehrfach in einer Liste vorkommen (Unterschied zu Mengen).

Listenzerlegung in Prolog

  • Prolog hat einen eingebauten Operator ‘|’ (Listenkonstruktor) mit dem man eine Liste in Kopf (head) und Rest (tail) zerlegen kann.
  • Der Kopf ist das erste Element der Liste.
  • Der Rest ist die Restliste.
?- [Head|Tail] = [mia, vincent, jules, mia].
Head = mia,
Tail = [vincent, jules, mia].
  • Eine leere Liste hat keinen Head und lässt sich somit nicht zerlegen:
?- [Head|Tail] = [].
false.
  • Man kann mit ‘|’ auch mehr als ein Element am Anfang abtrennen:
?- [First,Second|Tail] = [mia, vincent, jules, mia].
First = mia,
Second = vincent,
Tail = [jules, mia].

Prädikat:member/2

member/2 ist ein rekursiv definiertes Prädikat, das überprüft, ob ein Element (ein Term) in einer Liste vorkommt:

% member/2, member(Term,List)
member(X,[X|_]).
member(X,[_|T]) :-
    member(X,T).
  • Der Fakt member(X,[X|_]). besagt, dass etwas ein Element einer Liste ist, wenn es das erste Element (der Head) der Liste ist.
  • Die Regel member(X,[_|T]):- member(X,T). besagt, dass etwas ein Element einer Liste ist, wenn es ein Element der Restliste (des Tails) ist.
  • Jedes Element einer Liste ist entweder erstes Element oder ein Element im Tail.
  • Vorsicht: member/2 ist ein in der Library lists vordefiniertes Prädikat, das von manchen Prologimplementierungen automatisch geladen wird. Verwenden sie daher besser einen anderen Namen, z.B. my_member/2.

Vorteile der deklarativen Programmierung

Die deklarative Logik von member/2 erfasst verschiedene Fälle, für die in prozeduralen Sprachen separate Prozeduren geschrieben werden müssten:

% Ist 1 in Liste [1,2,3]?
?- member(1,[1,2,3]).
% In welchen Listen ist 1?
?- member(1,L).
% Welche X sind in [1,2,3]?
?- member(X,[1,2,3]).
% In welchen Listen ist X?
?- member(X,L).

Interne rekursive Listenrepräsentation

Prolog behandelt nichtleere Listen intern als zweistellige zusammengesetzte Terme mit Funktor ’[|]’.

?- [a,b]=’[|]’(a,’[|]’(b,[])).
true.

Nichtleere Listen werden dabei in Kopf und Rest zerlegt.

Der Rest ist entweder die leere oder wiederum eine nichtleere Liste.

’[|]’(a, [])
’[|]’(a, ’[|]’(b, []))
’[|]’(a, ’[|]’(b, ’[|]’(c, [])))
[a | []]
[a | [b | []]]
[a | [b | [c | []]]]
[a]
[a,b]
[a,b,c]

Listen können als binäre Bäume aufgefasst werden:

Unifikation / Matching von Listen

Zwei Listen sind unifizierbar,

  • wenn sie dieselbe Länge haben und
  • wenn die korrespondierenden Listenelemente unifizierbar sind.
?- [a,b,X]=[Y,b,3].
X = 3, Y=a
?- [[a,b,c],b,3]=[Y,b,3]. Y = [a, b, c]
?- [a,b,c] = X. % Variablen koennen mit Listen unifiziert werden. X=[a,b,c]
?- [a,b,X,c]=[Y,b,3].
false.
?- [a,c,3]=[Y,b,3].
false.
  • Die Länge einer Lister ist die Zahl ihrer Elemente.

Anonyme Variable

  • Die Variable ‘_’ ist die anonyme Variable in Prolog.
  • Sie kommt immer dann zum Einsatz, wenn ein Wert zwar variabel sein soll, später aber nicht mehr benötigt wird.
  • Die anonyme Variabel erhöht die Lesbarkeit der Programme.
  • Anders als bei anderen Variablen ist jedes Vorkommen der anonymen Variabel unabhängig von den anderen. Sie kann also immer wieder anders initialisiert werden:
isst_gerne(X,X) = isst_gerne(a,b).
false.
isst_gerne(_,_) = isst_gerne(a,b).
true.

Hinweis: Variablen wie _X, die mit einem Unterstrich beginnen sind nicht anonym, sie führen aber im Gegensatz zu anderen Variablen beim Konsultieren einer Wissensbasis nicht zu der Warnung: „singleton variables:“.

Beispielanfrage:member/2

?- member(c,[a,b,c,d]).
% Die erste Klausel passt nicht, aber die zweite. Weiter geht es mit:
member(c,[b,c,d]).
% Und wieder passt nur die rekursive Klausel und es geht weiter mit:
member(c,[c,d]).
% Jetzt passt die erste Klausel (c ist das erste Element der Liste). Wir bekommen:
?- member(c,[a,b,c,d]).
true.   
?- member(c,[a,b]).
% Die erste Klausel passt nicht, aber die zweite. Weiter geht es mit:
member(c,[b]).
% Und wieder passt nur die rekursive Klausel und es geht weiter mit:
member(c,[]).
% Jetzt passt keine der beiden Klauseln, da die Liste leer ist. Wir bekommen:
?- member(c,[a,b]).
false.

rekursive Listenverarbeitung an einem Beispiel

Die Definition von Prädikaten, die Listen rekursiv verarbeiten, gehört zu den zentralen Aufgaben in der Prologprogrammierung.

Beispiel: Prädikat a2b/2, das zwei Listen L1 und L2 nimmt und genau dann zutrifft, wenn beide Listen gleich lang sind und L1 nur aus a’s und L2 nur aus b’s besteht.

Vorgehensweise: Zunächst sollte man sich möglichst verschiedene positive und negative Beispiele für die Belegungen der Variablen L1 und L2 überlegen:

% negative Beispiele
?- a2b([a,c,a],[b,b,b]). % L1 besteht nicht nur aus a’s
false.
?- a2b([a,a,a],[b,c,b]). % L2 besteht nicht nur aus b’s
false.
?- a2b([a,a],[b,b,b]). % L1 ist kuerzer als L2
false.
?- a2b([a,a,a],[b,b]). % L1 ist laenger als L2
false.
?- a2b(t,[b,b]). % L1 ist keine Liste
false.
?- a2b([a,a],t). % L2 ist keine Liste
false.

Ausgehend von dieser Aufstellung möglicher Anfragen ist es oft relativ einfach, die Ankerklausel zu formulieren: der Fall mit den einfachsten Listen, die zu einem true führen.

a2b([],[]).

Anschließend benötigt man noch die rekursive Klausel: zwei Listen erfüllen die Bedingung des Prädikats a2b/2, wenn die erste Liste mit einem a beginnt und die zweite mit einem b und die Restlisten die Bedingung a2b/2 erfüllen:

a2b([],[]).
a2b([a|T1],[b|T2]):-
    a2b(T1,T2).

Abschließend sollte man immer die Prädikatsdefinition mit den zuvor aufgestellten Positiv- und Negativbeispielen testen.