AutoLISP bietet einige weitere Funktionen zur Bearbeitung
von Listen, die bisher nicht besprochen wurden. Es handelt
sich dabei um
(mapcar),
(apply),
(foreach)
und
(assoc). Wir werden uns zunächst mit
(mapcar)
und
(apply) befassen und dabei auch
(lambda)
mit besprechen, das zwar keine Listenbearbeitungsfunktion ist,
aber sehr oft im Zusammenhang mit
(mapcar) verwendet
wird. Die gemeinsame Behandlung bietet sich also an. Als letztes
Kapitel über Listenbearbeitung werden wir uns dann mit
(assoc) und den Assoziationslisten befassen. Diese Art
von Listen ist der Schlüssel zur AutoCAD-Geometriedatenbank.
Die Funktion
(mapcar) wendet eine andere Funktion auf
Listen an.
(mapcar) erhält mindestens zwei Argumente,
nämlich einen Ausdruck, der die anzuwendende Funktion repräsentiert,
und eine Liste, auf die die Funktion anzuwenden ist.
Ein Beispiel - zuächst einmal erzeuegen wir eine Liste mit
ein paar Zahlen:
(setq zahlenliste '(4 15 3 7 11))
=> (4 15 3 7 11)
Wir wollen alle Zahlen in dieser Liste um eins erhöhen. Dazu
bietet sich natürlich die Inkrementfunktion
(1+ ...) an.
Wenden wir also mit Hilfe von
(mapcar) diese Funktion
auf die Liste
zahlenliste an:
(mapcar '1+ zahlenliste)
=> (5 15 4 8 12)
(mapcar) evaluiert alle seine Argumente. Daher müssen
wir das Symbol
1+ quotieren. Das Symbol
zahlenliste
darf natürlich nicht quotiert werden, da wir ja
(1+)
nicht auf das Symbol, sondern den daran gebundenen Wert anwenden
möchten. Dieser gebundene Wert ist die Liste, die wir erzeugt haben.
Im Prinzip kann
(mapcar) eine unbegrenzte Anzahl von
Argumenten erhalten. Praktisch wird aber die Anzahl der Argumente
durch die Funktion bestimmt, die von
(mapcar) auf die
weiteren Argumente angewendet werden soll. In unserem Beispiel
war
(1+) die auszuführende Funktion. Da
(1+) immer
nur ein Argument erhält, müssen wir genau eine Liste an
(mapcar) mitschicken, aus der dann nacheinander die Argumente
für
(1+ ...) entnommen werden.
Ein anderes Beispiel: Wir wollen eine Liste, die mehrere
Winkel in Grad enthält, in das Bogenmass konvertieren. Dazu brauchen
wir zunächst eine Umrechnungsfunktion. Diese definieren wir so:
(defun grad-zu-bogen(winkel)
(* pi(/ winkel 180))
)
Wir gehen nun davon aus, dass eine Liste namens
gwliste eine
uns unbekannte Anzahl von Grad-Winkeln enthält, die umgerechnet und
in
bwliste gespeichert werden sollen. Ohne Inhalt oder Länge
von
gwliste zu kennen, können wir die Umrechnung durchführen.
(setq bwliste
(mapcar 'grad-zu-bogen gwliste)
)
Auch in diesem Beispiel wurde nur eine Liste an
(mapcar)
übergeben, da auch die Funktion
(grad-zu-bogen) nur ein
Argument erwartet. Doch nun zu einem Beispiel, bei dem die
Ausführungs-Funktion zwei Argumente erwartet. Nehmen wir dazu die
Funktion
(+ ...). Natürlich kann
(+ ...) auch mehr
als zwei Argumente erhalten, wir beschränken uns aber erst einmal
auf zwei. Wir brauchen also als Argumente für
(mapcar) zwei
Listen mit Zahlen.
(setq
zahlen1 '(4 2 11 14 3 8)
zahlen2 '(6 6 2 11 18 5)
)
Nun wenden wir
(mapcar) an und erhalten diese Liste als
Rückgabe:
(mapcar '+ zahlen1 zahlen2)
=> (10 8 13 25 21 13)
Noch ein weiteres Beispiel zur Verdeutlichung: AutoCAD-Punkte
werden als Listen mit drei Zahlen verarbeitet.
(car)
gibt uns die X-Koordinate,
(cadr) den Y-Wert und
(caddr)
den Z-Wert (Dieser muss nicht unbedingt vorhanden sein - AutoCAD
akzeptiert auch 2D-Punkte, Z wird dann automatisch auf den Wert der
Systemvariablen "ELEVATION" gesetzt.)
Die Aufgabenstellung ist nun folgende: In einer Liste, von der uns
nur der Name bekannt ist, sind Punkte gespeichert. Wir müssen aus
irgendwelchen Gründen überprüfen, ob der Y-Wert 4 doppelt
vorhanden ist. Um diese Prüfung durchzuführen, brauchen wir keine
Programmschleife zu schreiben, wenn wir ein bisschen geschickt
vorgehen. Wir klären zunächst, wie wir eine Liste bekommen, die nur
die Y-Koordinaten enthält. Ganz einfach, wir wenden
(cadr)
mit
(mapcar) auf die Liste an. Obwohl wir den Inhalt der Liste
als unbekannt voraussetzten, folgt hier ein kleines Ersatzbeispiel
mit bekannten Werten, damit das Beispiel nachzuvollziehen ist:
(setq punkteliste
'( (3 12 0)
(8 4 0)
(9 2 0)
(5 5 0)
(2 4 0)
(1 1 0)
(3 0 0)
)
)
(mapcar 'cadr punkteliste)
=> (12 4 2 5 4 1 0)
Es ist offensichtlich, dass der Y-Wert 4 doppelt vorkommt. Wie
kann man dies aber in einer unbekannten Liste testen? Wir nutzen
dazu die Funktion
(member). Wie wir wissen, gibt
(member)
den Rest der Liste ab der Stelle zurück, an der die 4 erstmals vorkommt.
(member 4(mapcar 'cadr punkteliste))
=> (4 2 5 4 1 0)
Um zu klären, ob die 4 doppelt vorhanden ist, müssen wir
(member)
einfach noch einmal anwenden, und zwar auf den Rest der Liste ohne die
erste gefundene 4. Diese entfernen wir mit
(cdr).
(member 4
(cdr
(member 4
(mapcar 'cadr punkteliste)
)
)
)
=> (4 1 0)
Das ist schon unser fertiger Prüfausdruck, den wir auf jede beliebige
Punkteliste anwenden können. Wir können sogar noch weiter gehen und
eine Testfunktion daraus machen, mit der wir jeden beliebigen Y-Wert
auf doppeltes Vorhandensein prüfen können:
(defun doppelter-ywert(ywert liste)
(member ywert
(cdr
(member ywert
(mapcar 'cadr liste)
)
)
)
)
Es sollte noch ein Wort über die Rückgabe verloren werden. Die
Funktion gibt dann, wenn der Y-Wert nicht doppelt vorkommt, nil
zurück. Ist er aber mindestens zweimal vorhanden, wird ein Listenrest
zurückgegeben. Dieser Listenrest ist genausogut wie
T oder
jedes andere Symbol, das nicht nil ist, zu interpretieren. Sie wissen
ja, dass in LISP alles wahr ist, was ungleich
nil ist.
Wir wenden unsere Testfunktion noch einmal auf unsere
Beispiel-Punkteliste an:
(doppelter-ywert 4 punkteliste)
=> (4 1 0)
(doppelter-ywert 5 punkteliste)
=> nil
Was passiert, wenn wir von
(mapcar) die Funktion
(+ ...)
auf zwei Listen anwenden lassen, die unterschiedlich lang sind?
(setq
zahlen1 '(4 2 11 14 3 8 10 3)
zahlen2 '(6 6 2 11 18 5)
)
(mapcar '+ zahlen1 zahlen2)
=> (10 8 13 25 21 13 10 3)
Wir erhalten hier ein korrektes Ergebnis: Da die Funktion
(+ ...)
auch dann definiert ist, wenn sie nur ein Argument erhält, gibt es
keine Fehlermeldung. Der Ablauf der
(mapcar)-Ausführung findet
also folgendermassen statt:
(+ 4 6) ; ergibt 10
(+ 2 6) ; ergibt 8
... bis ...
(+ 8 5) ; ergibt 13
(+ 10) ; ergibt 10
(+ 3) ; ergibt 3
Verwenden wir allerdings eine Funktion, die zwingend zwei Argumente
vorschreibt, dann müssen auch die beiden Listen gleichlang sein.
(setq symbole '(a b c d e)) => (a b c d e)
(setq werte '(1 2 3 4 5)) => (1 2 3 4 5)
(mapcar 'set symbole werte) => (1 2 3 4 5)
!a => 1
!e => 5
In diesem Beispiel haben wir eine Liste mit Symbolnamen und
eine Liste mit Werten angelegt. Durch die Anwendung von
(mapcar) haben wir dann den Symbolen die Werte zugeordnet.
(set) gibt immer den zugeordneten Wert zurück, daher
ist die Rückgabe von
(mapcar) eine Liste mit den Rückgaben
der 5
(set)-Aufrufe. Die Rückgabe ist hier aber nebensächlich,
da bei Anwendung von
(set) der Nebeneffekt die Hauptsache ist.
Wenn wir in diesem Fall
(mapcar) mit zwei unterschiedlich
langen Listen aufgerufen hätten, wäre die Sache mit einer
Fehlermeldung beendet worden.