map()
Diese Seite behandelt ein äußerst elegantes Sortierverfahren: die "Schwartz'sche Transformation" (nach Randal L. Schwartz). Obwohl zunächst recht aufwendig aussehend, offenbahrt dieses Konstrukt ungeahnte Möglichkeiten, hat man es erst einmal verstanden.
Im Grunde genommen handelt es sich nur um einen von vielen Wegen Daten zu sortieren. Allerdings ist es was Flexibilität und Effizienz angeht, wohl allen anderen Lösungen überlegen. Ein ähnliches Verfahren, das auf demselben Prinzip basiert, ist unter der Bezeichnung "Decorate-Sort-Undecorate" (DSU) bekannt.
Zunächst ein paar Worte dazu, wie in Perl Daten sortiert
werden. Um den internen Algorithmus braucht man sich normalerweise
keine Gedanken machen. Für den Benutzer bzw. Programmierer
reduziert sich die Sortierung auf den Vergleich zweier
Datenelemente - ist nichts anderes angegeben, d.h., ruft
man einfach "sort @liste
" auf, so
erfolgt die Sortierung gemäß ASCII-Code in aufsteigender
Reihenfolge.
Durch Angabe einer eigenen Subroutine läßt
sich die Sortierung gezielt steuern. Die dort definierten Kommandos
werden während des Sortierens mehrfach aufgerufen, wobei jedesmal
zwei Elemente (dargestellt durch $a
und $b
)
miteinander verglichen werden. Ist der Rückgabewert gleich 1, so gilt
das erste Element ($a
) als "größer" im
Sinne der gewünschten Sortierung. Bei -1 geht Perl davon aus, daß
$b
"größer" ist, bei 0 wird "Gleichheit"
angenommen.
Beispiele:
|
|
Am letzten dieser Beispiele kann man schon ein Problem erkennen:
jedesmal, wenn zwei Daten miteinander verglichen werden, muß
die Funktion substr()
ausgeführt werden. Dies
kann bei großen Datensätzen dazu führen, daß
die meiste Rechenzeit in Operationen auf einzelne Elemente
verbraucht wird, denn jedes einzelne Datum wird i.a. während
der Sortierung mehr als einmal zu einem Vergleich herangezogen
(wer es genauer wissen will: bei n Daten im Mittel
nlogn-mal).
Eine Lösung des oben beschriebenen Problems besteht darin, in
einem ersten Schritt zunächst
für jedes Element des Datensatzes die entsprechende Operation
(hier: substr()
) durchzuführen, anschließend
eine Sortierung dieser temporären Daten vorzunehmen und
schließlich von diesen wieder zu den
ursprünglichen Daten zurückzukehren.
Als Beispiel soll nun eine Datei dienen, aus deren Zeilen jeweils eine Zahl extrahiert werden muß, die dann als Suchkriterium dient. Der Inhalt einer solchen Datei sähe beispielhaft etwa so aus:
oexkwch<37>jy
yunq<100>zmwi
ikbkwe<545>bcljvbry
ojudnle<818>tgum
gpmlxp<972>lud
Erzeugen kann man sich derartige Daten mit diesem Programm:
|
Ein Ansatz zum Sortieren dieser Daten könnte so aussehen:
|
Dabei wird allerdings viel Rechenzeit durch die vielfache Auswertung der
regulären Ausdrücke in by_number
verbraucht.
Schneller geht es, wenn man aus jedem Datum ein zweielementiges
(anonymes) Array konstruiert, dessen eines Element das Datum selbst und das
andere das Ergebnis des regulären Ausdruckes (hier: die Zahl)
ist.
|
Im obigen Skript wird in der ersten foreach
-Schleife ein
Array namens @temp_1
aufgebaut, dessen Elemente
jeweils Referenzen auf zweielementige anonyme Arrays sind. Diese
zweielementigen Arrays enthalten unter dem Index 0 die ursprüngliche
Zeile und unter dem Index 1 die extrahierte Zahl.
Beim sort
sind nun die beiden zu vergleichenden Elemente
in den Variablen $a
und $b
die Referenzen
aus dem Array @temp_1
. Auf die für die Sortierung
benutzte Zahl (unter dem Index 1) wird dann durch $a->[1]
bzw. $b->[1]
zugegriffen. <=>
sorgt dann wie gewohnt für die (numerische) Sortierung der
beiden Zahlen.
Danach befindet sich in @temp_2
wiederum eine Liste
aus Referenzen auf zweielementige anonyme Arrays, allerdings nun
nach den jeweiligen Zahlen sortiert.
In der abschließenden foreach
-Schleife
wird nun aus @temp_2
jeweils das erste Element
dereferenziert und in das Array @ergebnis
gepackt.
Dieses Array enthält dann die ursprünglichen Datenzeilen,
nun aber gemäß der enthaltenen Zahlen sortiert.
map()
Mit Hilfe von map()
kann man auf einfache Art und Weise
eine Operation auf alle Elemente einer Liste anwenden. map()
erwartet entweder einen Ausdruck oder einen Programmblock, der dann
nacheinander für jedes Arrayelement, auf das über $_
zugegriffen wird, aufgerufen wird.
Beispiel:
|
|
Zunächst wird jedes Element mit sich selbst multipliziert und
die Ergebnisliste wieder in @liste
gespeichert.
Anschließend wird mit Hilfe der Funktion sqrt()
jeweils die Quadratwurzel gezogen, wodurch sich wieder die
ursprünglichen Werte ergeben.
Die Schwartz'sche Transformation nutzt die Möglichkeiten der
map
-Funktion aus, um die ganze Sortierung in
einer Befehlszeile unterzubringen und ohne explizit
temporäre Arrays zu verwenden.
Damit reduziert sich das letzte Sortierbeispiel auf diesen Code:
|
Man beachte dabei, daß die letzten drei Zeilen des Skriptes
nur ein Perl-Kommando darstellen,
das sozusagen von hinten
gelesen werden muß: zuerst wird aus @liste
ein
Array aus Referenzen auf zweielementige Arrays erstellt. Dieses
Array ist dann das Argument von sort
und aus dessen
Rückgabewert wiederum wird das jeweils erste Element (mit dem
Index 0) extrahiert. Das dabei entstehende Array wird schließlich
@ergebnis
zugewiesen.
Wie schon weiter oben erwähnt war das Hauptziel der Schwartz'schen Transformation eine Steigerung der Effizienz. Dies läßt sich mit Hilfe des Benchmark-Moduls, das der Perl-Distribution beiliegt, recht einfach überprüfen.
|
|
Wie man sieht, benötigt die Schwartz'sche Transformation in diesem
Falle nur etwa
ein Fünftel der Rechenzeit im Vergleich zum einfachen
"sort by_number
".
Autor: Eike Grote | Version: 2.07 (27.10.2019) |