




Neben Arrays und Listen gibt es in Perl eine weitere Datenstruktur, in der Sie Daten auflisten können: die assoziativen Arrays, kurz Hashes genannt. In vielen Situationen - abhängig von der Art der Daten und ihrer Verwendung - eignen sich Hashes weit besser als Arrays, um Daten zu speichern und auf sie zuzugreifen.
Heute befassen wir uns mit Hashes. Die Themen sind heute:
split einen String in eine Liste (oder einen Hash) konvertieren
   
Sie haben gestern gelernt, dass eine Liste ein Satz von Skalaren und ein Array eine (nach Elementposition) geordnete Liste ist. Mit einem Hash kann man ebenfalls eine Sammlung von Daten darstellen, jedoch werden die Daten auf eine andere Art organisiert.
Ein Hash ist ein ungeordneter Satz von Paaren aus Schlüsseln und Werten. Jedem Schlüssel ist ein Wert zugeordnet, wobei sowohl Schlüssel als auch Wert eine beliebige Art von skalarem Wert sein können (siehe Abbildung 5.1). Sie können auf ein Element (sprich ein Paar) in einem Hash zugreifen, indem Sie sich auf den Schlüssel beziehen.
Weder die Schlüssel noch die Werte stehen in irgendeiner Ordnung - Sie können nicht auf das erste oder letzte Element in einem Hash verweisen.
Hashes sind in vieler Hinsicht nützlicher als Arrays, vor allem wenn man auf ein Element lieber mit einer expliziten Bezeichnung (einem Hash-Schlüssel) als mit einer bloßen Nummer (einem Array-Index) zugreifen möchte.


Hashes werden auch assoziative Arrays genannt. »Assoziatives Array« ist sogar die ursprüngliche, korrekte Bezeichnung, die im Grunde besser beschreibt, was Hashes eigentlich sind (die Schlüssel werden mit ihren Werten assoziiert). Doch mittlerweile bevorzugen viele Perl-Programmierer (wie ich auch) den viel kürzeren und weniger zungenbrecherischen Namen Hash.
  Wie Arrays haben Hashes ihre eigenen, als solche gekennzeichneten Variablen: Diese 
beginnen mit einem Prozentzeichen (%) und folgen denselben Regeln wie 
Arrayvariablen. Wie bei allen Variablen ist die Hash-Variable %x etwas anderes als die 
Arrayvariable @x oder die Skalarvariable $x.
Hashes sind Listen und den Arrays in Erstellung und Gebrauch sehr ähnlich. Doch weil Hashes ihre Daten anders speichern, haben sie ein paar Besonderheiten. Wenn Sie zum Beispiel Daten in ein Hash packen, müssen Sie bei jedem Element auf zwei Skalare achten (den Schlüssel und den Wert). Und weil Hashes nicht geordnet sind, ist das Sortiern von Hash-Elementen etwas aufwendiger. Außerdem verhalten sich Hashes in skalarem Kontext anders als Arrays. Lesen Sie weiter, ich werde Ihnen all dies jetzt erklären.
Die Listensyntax (Klammer auf, durch Kommata getrennte Elemente, Klammer zu) kann zum Erstellen von Hashes ebenso verwendet werden wie für Arrays. Alles was Sie tun müssen, ist, Ihre Daten innerhalb von Klammern aufzulisten und dann eine Hash-Variable auf die linke Seite der Zuweisung zu stellen:
%paare = ('rot', 255, 'grün', 150, 'blau', 0);
  Mit einer Arrayvariablen ergäbe dies ein Array von sechs Elementen. Mit einer 
HashVariablen (%paare) werden die Elemente dem Hash paarweise zugewiesen: Das 
erste Element ist ein Schlüssel, das zweite sein Wert, das dritte Element ist der zweite 
Schlüssel und das vierte dessen Wert und so weiter und so fort. Wenn die Anzahl der 
Elemente ungerade ist, wird das letzte ignoriert.
Nun kann man bei dieser Schreibweise kaum auf den ersten Blick erkennen, was in der Liste ein Schlüssel und was ein Wert ist (und je länger die Liste ist, mit der Sie den Hash initialisieren, desto schwieriger wird es). Viele Perl-Programmierer setzen deswegen in ihrer Listensyntax für Hashes die Schlüssel/Wert-Paare jeweils in eigene Zeilen:
%temps = (
'Boston', 22,
'New York', 18,
'Miami', 32,
'Portland', 25,
# und so weiter...
);
  Noch besser als diese Formatierung ist der =>-Operator, der sich exakt genauso 
verhält wie das Komma zwischen Schlüssel und Wert. Die Verbindung zwischen den 
Schlüsseln und Werten wird durch den =>-Operator noch deutlicher. Das erste 
Beispiel mit den Farben sieht dann wie folgt aus:
%paare = ('rot'=>255, 'grün'=>150, 'blau'=>0);
Und das zweite mit den Städten:
%temps = (
'Boston' => 22,
'New York' => 18,
'Miami' => 32,
'Portland' => 25,
# und so weiter...
);
Und noch etwas: Perl geht bei jedem Schlüssel eines Hash-Elements davon aus, dass es sich um Strings handelt. Und weil der Schlüssel ohnehin ein String sein muss, können Sie sich etwas Tipparbeit sparen und die Anführungszeichen auch weglassen:
%paare = (rot=>255, grün=>150, blau=>0);
Wenn der Schlüssel allerdings ein Leerzeichen enthält, müssen Sie die Anführungszeichen setzen (so smart ist Perl auch wieder nicht).
  Wie bei Arrays erzeugt das Zuweisen einer leeren Liste () an eine Hash-Variable 
einen leeren Hash:
%hash = (); #keine Schlüssel und keine Werte
Eine zweite Möglichkeit, einen Hash zu erzeugen, ist die Initialisierung mit einem Array oder einer Liste. Weil die »Rohform« der Inhalte von Hashes und Arrays jeweils Listen sind, können Sie ohne Probleme zwischen den beiden hin- und herkopieren:
@zeug = ('eins', 1, 'zwei', 2);
%paarweises_zeug = @zeug
  In diesem Beispiel werden durch die Zuweisung des Arrays @zeug an den Hash 
%paarweises_zeug die Array-Elemente in eine Liste expandiert, dann in zwei 
Schlüssel/Wert-Paare zerlegt und im Hash gespeichert. Das Ergebnis wäre das 
gleiche, wenn Sie alle Elemente in Listenform eingetippt hätten. Passen Sie aber auf 
die Anzahl der Elemente auf - ist sie ungerade, wird das letzte Element ignoriert und 
nicht in den Hash übernommen (Perl-Warnungen geben Ihnen Bescheid, wenn das 
passiert).
Und wie ist es mit der Rückwandlung von einem Hash in eine Liste? Im folgenden Beispiel weisen Sie einen Hash einem Array zu:
@zeug = %paarweises_zeug;
  Wenn Sie einen Hash auf die rechte Seite einer Listenzuweisung stellen oder vielmehr 
wann immer Sie einen Hash in einer Situation verwenden, in der eine rohe Liste 
erwartet wird, »dröselt« Perl den Hash in seine einzelnen Elemente auf (Schlüssel, 
Wert, Schlüssel, Wert und so fort). Diese Liste wird dann dem Array @zeug 
zugewiesen.
Dieses schöne Aufschlüsselungsverhalten hat einen Haken: Weil Hashes nicht geordnet sind, stehen die Schlüssel/Wert-Paare, die Sie aus einem Hash herausziehen, höchstwahrscheinlich weder in derselben Reihenfolge, in der Sie sie eingefügt haben, noch sind sie nach einem anderen augenscheinlich sinnvollen Kriterium sortiert. Hash-Elemente werden in einem internen Format gespeichert, das den Zugriff sehr schnell vornimmt (die Geschwindigkeit ist sozusagen Perls einziges Anordnungskriterium), und in dieser internen, so gut wie unvorhersagbaren Reihenfolge werden sie auch wieder hervorgeholt. Wenn Sie eine Liste aus einem Hash in einer bestimmten Reihenfolge brauchen, müssen Sie eine Schleife bauen, die die Elemente nach Ihren Vorgaben gezielt aus dem Hash zieht (mehr darüber später).
Anders als Arrays, die lediglich Werte in einer bestimmten Reihenfolge enthalten, bestehen Hashes wie gesagt aus Schlüssel/Wert-Paaren. Um auf einen Wert in einem Hash zuzugreifen, müssen Sie seinen Schlüssel (auch Key genannt) kennen. Mit dem Schlüssel in geschweiften Klammern ({}) können Sie folgendermaßen auf einen Hash- Wert zugreifen:
print $temps{'Portland'};
$temps{'Portland'} = 50;
  Sie sehen, dass diese Syntax der Array-Zugriffssyntax $array[] sehr ähnlich ist - Sie 
greifen mit einer Skalarvariablen ($temps) und geschweiften Klammern um den 
Schlüsselnamen (anstatt eckiger um einen Array-Index) auf einen skalaren Wert 
innerhalb des Hash zu. Der Schlüssel innerhalb der Klammern sollte ein String sein 
(hier haben wir einen single-quoted String genommen), Ziffern werden 
gegebenenfalls in Strings umgewandelt. Außerdem können Sie, wenn der Schlüssel 
nur ein einziges Wort enthält, die Anführungszeichen weglassen, Perl versteht auch 
so, was Sie meinen:
$temps{Portland} = 50;    # ist das gleiche wie $temps{'Portland'} = 50;
Wie bei den Arrays kommt der Variablenname in der Zugriffssyntax nicht mit gleichnamigen Skalarvariablen in Konflikt. Jede der folgenden Variablen verweist auf etwas anderes, obwohl der Variablenname immer derselbe ist:
$name # ein Skalar
@name # ein ganzes Array
%name # ein ganzer Hash
$name[$index] # der Skalar im Array @name an der Stelle $index
$name{'key'} # der Skalar im Hash %name mit dem Schlüssel 'key'
  Mit der eben besprochenen Elementzugriffssyntax können Sie ein Hash-Element 
hinzufügen, darauf zugreifen und es ändern. Aber wie werden Sie Elemente, die Sie 
nicht mehr brauchen, wieder los? Dafür stellt Perl die Funktion delete zur Verfügung. 
Diese Funktion nimmt den Verweis auf ein Hash-Element entgegen (im allgemeinen 
einen Hash-Zugriffsausdruck wie $hashname{'key'}), löscht sowohl den Schlüssel als 
auch den ihm zugeordneten Wert und gibt den gelöschten Wert zurück. Das bedeutet, 
dass Sie mit delete ein Element nicht nur löschen, sondern auch von einem Hash zu 
einem anderen verschieben können (es also aus dem einen löschen und dem anderen 
hinzufügen), wie in folgendem Beispiel:
$hash2{$key} = delete $hash{$key};
  Sie können auch überprüfen, ob ein bestimmtes Schlüssel/Wert-Paar in einem Hash 
existiert: Die Funktion exists durchsucht einen Hash nach einem ihr übergebenen 
Schlüssel und gibt wahr zurück, wenn sie ihn findet. Beachten Sie, dass der zu dem 
gefundenen Schlüssel zugehörige Wert sehr wohl undefiniert sein könnte - exists 
prüft wirklich nur, ob der Schlüssel vorhanden ist. Verwenden Sie exists 
folgendermaßen.
if (exists $hashname{$key})  { $hashname{$key}++; }
  Dieser Ausdruck prüft, ob der Schlüssel $key vorhanden ist, und wenn ja, 
inkrementiert er den diesem Schlüssel zugeordneten Wert (vorausgesetzt natürlich, 
dieser Wert ist eine Zahl).
  Angenommen, Sie möchten alle Werte in einem Array oder einer Liste durchgehen, 
jeden einzelnen auf eine bestimmte Eigenschaft überprüfen und unter bestimmten 
Bedingungen verwenden. Bei einem Array würden Sie mit Element 0 anfangen und 
den Vorgang so lange wiederholen, bis Sie beim letzten Element der Liste angelangt 
sind (oder eine foreach-Schleife verwenden). Aber wie geht das mit Hashes? Eine 
Reihenfolge gibt es nicht, und die Schlüssel können irgendwelche skalaren Werte sein. 
Was Sie brauchen, ist eine Methode, ein paar Informationen aus dem Hash zu ziehen, 
mit denen Sie die Struktur dann durchlaufen können.
  Für dieses Problem stehen die Funktionen keys und values zur Verfügung. Diese 
Funktionen nehmen beide einen Hash als Argument entgegen und geben eine Liste 
zurück - keys eine Liste der Schlüssel und values eine Liste der Werte im 
angegebenen Hash. Wenn Sie dann mit dieser Liste und foreach oder einer anderen 
Schleife auf jedes einzelne Hash-Element zugreifen, kommen Sie an wirklich alle 
Elemente - auch die, die Sie vielleicht längst vergessen haben.
  Nehmen wir zum Beispiel einen Hash mit einer nach Städtenamen aufgeschlüsselten 
Liste von Temperaturen (wie wir es bereits vorhin in einem Beispiel hatten). Diese 
Liste möchten wir jetzt alphabetisch nach Städten sortiert ausgeben. Dafür erstellen 
wir mit keys eine Liste aller Schlüssel, sortieren diese Liste mit sort und geben die 
sortierten Schlüssel und ihre Werte dann in einer foreach-Schleife aus, etwa so:
foreach $stadt (sort keys %temps) {
    print "$stadt: $temps{$stadt} grad\n";
}
  Diese Schleife durchläuft nacheinander jedes Element der sortierten Schlüsselliste und 
weist es der Variablen $stadt zu. Im Schleifenkörper können Sie sich dann mit dieser 
Variablen auf das aktuelle Element beziehen.
Lassen Sie uns auf den Kontext zurückkommen und betrachten, wie Hashes sich in den verschiedenen Kontexten verhalten. Zumeist gelten dieselben Regeln wie für Listen, doch es gibt ein paar Ausnahmen.
Ich habe Ihnen gezeigt, wie man mit Listensyntax einen Hash erstellt, wobei der Hash die Elemente dann als Schlüssel/Wert-Paare speichert wie hier:
%paare = (rot=>255, grün=>150, blau=>0);
Im umgekehrten Fall, wenn Sie einen Hash verwenden, wo eine Liste erwartet wird, wird der Hash (in beliebiger Reihenfolge) zurück in seine Einzelteile zerlegt und folgt dann den gleichen Regeln wie alle anderen Listen.
@farben = %paare; # ergibt ein Array aus allen Elementen
($x, $y, $z) = %paare; # die ersten drei Elemente des aufgelösten
# Hash werden Variablen zugewiesen,
# verbleibende Elemente werden ignoriert
print %paare; # zerlegt den Hash in seine Elemente und
# gibt sie aus
Immer wenn Sie einen Hash in einem Listenkontext verwenden - zum Beispiel auf der rechten Seite einer Zuweisung an ein Array -, wird der Hash in eine Liste seiner Einzelteile »aufgedröselt«, und diese Liste benimmt sich dann wie in jedem anderen Listen- oder skalaren Kontext auch. Es gib allerdings einen Sonderfall:
$x = %paare;
  Auf den ersten Blick könnte man meinen, dies sei das Hash-Äquivalent zu $x = 
@array (Sie erinnern sich, damit ermitteln Sie die Anzahl der Elemente in einem 
Array). Aber Perl verhält sich hier anders als bei Arrays - das Ergebnis $x ist hier 
nämlich eine Beschreibung des internen Zustands der Hash-Tabelle, was in 99 % der 
Fälle wahrscheinlich nicht das ist, was Sie wollen. Um die Anzahl der Elemente 
(Schlüssel/Wert-Paare) in einem Hash zu erhalten, verwenden Sie statt dessen die 
keys-Funktion und weisen die Schlüsselliste einer Skalarvariablen zu:
$x = keys %paare;
  Die Funktion keys gibt eine Liste der Schlüssel im Hash zurück, die dann in skalarem 
Kontext ausgewertet die Anzahl der Elemente liefert.

Sind Sie neugierig, was ich mit »eine Beschreibung des internen Zustands der Hash-Tabelle« meine? Okay, dann werde ich es kurz erklären. Die Zuweisung einer Hash-Variablen in skalarem Kontext liefert Ihnen zwei Zahlen, getrennt durch einen Schrägstrich. Die zweite Zahl ist die Summe der zur Verfügung stehenden Slots, das heißt Speicherstellen, die für die interne Hashtabelle reserviert wurden (oft auch »buckets« genannt). Die erste Zahl ist die Anzahl der tatsächlich von den Daten genutzten Slots. Sinnvoll werden diese beiden Zahlen, wenn Sie wissen möchten, wie effizient eine Hash-Tabelle ist: Eine Hash-Beschreibung von 4/100 würde bedeuten, dass der Hash nur 4 von 100 bereitgestellten Buckets verwendet: schlechte Nachricht über die Effizienz Ihres Skripts. Den Aufbau fortgeschrittener - und effizienter - Datenstrukturen werden wir an Tag 19 behandeln.
Ändern wir noch einmal unser Statistikskript. Erweitern wir es diesmal dahin, dass es sich merkt, wie oft jede Zahl jeweils eingegeben wurde, und das Ergebnis schließlich als Balkendiagramm darstellt. Hier ein Beispiel, wie das Diagramm aussehen könnte (abgesehen von diesem Histogramm ist die Ausgabe dieselbe wie vorher; deswegen werde ich das hier nicht noch einmal alles aufführen):
Häufigkeit der einzelnen Zahlen:
1 | *****
2 | *************
3 | *******************
4 | ****************
5 | ***********
6 | ****
43 | *
62 | *
Um das Vorkommen jeder Zahl in unserem Skript zu verfolgen, verwenden wir einen Hash mit den neu eingegebenen Zahlen als Schlüssel und den Häufigkeiten, mit denen die Zahlen auftauchen, als Werte. Der Diagrammabschnitt des Skripts durchläuft dann alle Elemente dieses Hash und gibt in einer grafischen Darstellung aus, wie oft jede Zahl eingegeben wurde.
Listing 5.1 zeigt den Perl-Code für unser neues Skript:
Listing 5.1: nochmehrstats.pl.
1: #!/usr/bin/perl -w
2:
3: $input = ''; # Benutzereingabe: Zahl
4: @nums = (); # Array: Zahlen;
5: %freq = (); # Hash: Zahl-Haeufigkeit
6: $count = 0; # Anzahl aller Zahlen
7: $sum = 0; # Summe
8: $avg = 0; # Durchschnitt
9: $med = 0; # Median
10: $maxspace = 0;# maximaler Platz für die Schluessel
11:
12: while () {
13: print 'Geben Sie eine Zahl ein: ';
14: chomp ($input = <STDIN>);
15: if ($input ne '') {
16: $nums[$count] = $input;
17: $freq{$input}++;
18: $count++;
19: $sum += $input;
20: }
21: else { last; }
22: }
23:
24: @nums = sort { $a <=> $b } @nums;
25: $avg = $sum / $count;
26: $med = $nums[$count /2];
27:
28: print "\n Anzahl der eingegebenen Zahlen: $count\n";
29: print "Summe der Zahlen: $sum\n";
30: print "Kleinste Zahl: $nums[0]\n";
31: print "Groesste Zahl: $nums[$#nums]\n";
32: printf("Durchschnitt: %.2f\n", $avg);
33: print "Mittelwert: $med\n\n";
34: print "Haeufigkeit der einzelnen Zahlen:\n";
35:
36: $maxspace = (length $nums[$#nums]) + 1;
37:
38: foreach $key (sort { $a <=> $b } keys %freq) {
39: print $key;
40: print ' ' x ($maxspace - length $key);
41: print '| ', '*' x $freq{$key}, "\n";
42: }
Dieses Skript unterscheidet sich nicht sehr vom vorigen; die einzigen Änderungen stehen in den Zeilen 5, 10, 17 und im letzten Abschnitt von Zeile 36 bis 42. Betrachten Sie diese Zeilen einmal etwas genauer, und beachten Sie auch, wo und wie sie sich in das Skript fügen, das wir bereits geschrieben haben.
  Die Zeilen 5 und 10 sind ganz einfach; sie definieren lediglich neue Variablen, die wir 
später im Skript verwenden: Der Hash %freq speichert die Häufigkeit der 
eingegebenen Zahlen, und die Variable $maxspace enthält einen Zwischenwert zur 
Formatierung des Diagramms (mehr dazu, wenn wir zum Erstellen des Diagramms 
kommen).
Zeile 17 ist da viel interessanter. Sie steht innerhalb der Schleife, mit der wir die Daten einlesen. In der Zeile davor haben wir die zuletzt eingegebene Zahl dem Zahlenarray hinzugefügt. Die Zahl selbst ist der Schlüssel, und wie oft sie bis jetzt eingegeben wurde, ist der Wert. In Zeile 17 verwenden wir die eingegebene Zahl als Schlüssel des Häufigkeits-Hash. Wenn die Zahl bereits als Schlüssel vorhanden ist, erhöhen wir den ihr zugeordneten Wert um 1 (mit dem ++-Operator). Wenn kein solcher Schlüssel existiert, unsere Zahl also noch nicht im Hash enthalten ist, fügen wir sie damit hinzu und erhöhen den Wert auf 1.
Bei jedem weiteren Durchlauf inkrementieren wir nur dann den Wert (sprich die Häufigkeit), wenn genau diese Zahl wieder in den Benutzereingaben auftaucht.
So haben wir nach Beendigung der Eingabeschleife schließlich einen Hash, der jede eingegebene Zahl genau einmal als Schlüssel und ihre jeweilige Häufigkeit als Werte enthält. Jetzt muss nur noch ein Diagramm mit diesen Daten ausgegeben werden.
Anstatt wie in den bisherigen Beispielen Schritt für Schritt die Zeilen 36 bis 42 durchzugehen, möchte ich Ihnen nun zeigen, in welchen Schritten ich diese Schleife geschrieben habe. So bekommen Sie einen Einblick in meine Denkweise und die Entstehung dieser Schleife. Damit wird deutlicher, warum sie ist, wie sie ist.
  Als erstes möchte ich die Werte in die richtige Reihenfolge bringen. Also fange ich mit 
einer foreach-Schleife an, ähnlich der, die ich heute im Abschnitt »Auf alle Werte in 
einem Hash zugreifen« beschrieben habe.
foreach $key (sort { $a <=> $b } keys %freq) {
  print "Schluessel: $key Wert: $freq{$key}\n";
}
  In dieser Schleife verwende ich foreach, um auf jeden Hash-Schlüssel zuzugreifen. In 
welcher Reihenfolge das geschieht, wird jedoch von dem eingeklammerten Ausdruck 
in der ersten Zeile kontrolliert. Der keys %freq-Teil erstellt eine Liste aller Schlüssel 
des Hash, sort sortiert sie (Sie erinnern sich, sort sortiert standardmäßig in ASCII-
Reihenfolge, erst das Hinzufügen von {$a <=> $b} erzwingt eine numerische 
Sortierung). Das Ergebnis ist, dass der Hash vom kleinsten zum größten Schlüssel 
durchgegangen wird.
Innerhalb der Schleife muss ich dann nur noch die Schlüssel und die Werte ausgeben. Mit ein paar einfachen Daten erhielte ich dann eine Ausgabe wie diese:
Schluessel: 2 Wert: 4
Schluessel: 3 Wert: 5
Schluessel: 4 Wert: 3
Schluessel: 5 Wert: 1
  Das ist zwar eine gute Darstellung der Werte im %freq-Hash, aber kein Histogramm. 
Mein zweiter Schritt ist die Veränderung der print-Anweisung. Ich verwende den 
wunderbaren String-Wiederholungsoperator (x), um die der Häufigkeit der Zahlen 
entsprechende Anzahl Sternchen auszugeben:
foreach $key (sort { $a <=> $b } keys %freq) {
  print '$key |', '*' x $freq{$key}, "\n";
}
Damit komme ich der Sache schon näher. Die Ausgabe sähe jetzt etwas so aus:
2 | ****
3 | *****
4 | ***
5 | *
Problematisch wird es aber, wenn die eingegebenen Zahlen größer als 9 sind. Wenn nicht alle Schlüssel gleich viele Stellen haben, würde mein schönes Diagramm geradezu aufgeschraubt. Wenn ich zufällig eine vierstellige Zahl zwischen meinen ein- und zweistelligen Zahlen hätte, sähe das Histogramm wie folgt aus:
2 | ****
3 | *****
4 | ***
5 | *
13 | **
24 | *
45 | ***
2345 | *
  Was also tun? Ich muss dafür sorgen, dass vor dem Pipe-Zeichen (|) immer die 
richtige Anzahl Leerzeichen steht, damit die Sternchen im Histogramm in derselben 
Spalte beginnen. Ich habe dieses Problem mit der Funktion length gelöst, die die 
Anzahl der Zeichen (genaugenommen der Bytes) in einem skalaren Wert liefert.
  Zunächst muss ich herausfinden, wie breit die Schlüsselspalte überhaupt sein soll, das 
heißt wie viele Stellen der »breiteste« Schlüssel hat. Ich nehme dafür die Länge der 
größten Zahl im Array @zahlen und addiere eine 1, um ein Leerzeichen am Ende 
anzuhängen:
$maxspace = (length $nums[$#nums]) + 1;
Innerhalb der Schleife verändere ich noch einmal meine Print-Anweisungen. Diesmal teile ich dem String-Wiederholungsoperator mit, wie viele Leerzeichen nach einem Schlüssel ausgegeben werden sollen - Spaltenbreite minus Schlüssellänge. So wird der Unterschied zwischen kleineren und der größten Zahl stets korrekt mit Leerzeichen aufgefüllt, und ich kann mit der Ausgabe der Pipes und Sternchen weitermachen:
foreach $key (sort { $a <=> $b } keys %freq) {
  print $key;                               # den Schluessel ausgeben
  print ' ' x ($max_breite - length $key);  # bis zur max.-Breite mit
                                            # Leerzeichen auffuellen
  print '| ', '*' x $freq{$key}, "\n";      # die Sternchen ausgeben
}
Jetzt sieht das Histogramm aus, wie ich es Ihnen ganz am Anfang gezeigt habe. Ich bin fertig. Den kompletten Code haben Sie in Listing 5.1 bereits gesehen.

Die Art, wie ich hier das Diagramm formatiert habe, ist nicht gerade elegant. Sie sollten sich diese Methode nicht zum Vorbild nehmen, wenn Sie das Ausgabeformat Ihrer Daten festlegen, insbesondere wenn Sie dabei mit mehr als den paar Zeichen in diesem Beispiel zu tun haben. Perl (Sie erinnern sich, die praktische Extraktions- und Report-Sprache) hat einen Satz spezieller Datenformatierungs-Prozeduren, mit denen sich solche Reports viel einfacher erstellen lassen. Im HTML-Zeitalter wird mit Perl-Formatierungen nicht mehr viel gearbeitet, doch an Tag 20 gebe ich Ihnen zumindest eine kleine Kostprobe.
Über die Tastatur eingegebene Daten sind meist recht unkompliziert zu verarbeiten - Sie brauchen sie nur einer Variablen zuweisen und können dann damit anstellen, was immer Sie wollen. Aber in vielen Fällen sind - insbesondere aus Dateien eingelesene - Eingabedaten nicht so einfach zu handhaben. Was ist, wenn Sie Daten mit nicht einer, sondern gleich zehn Zahlen pro Zeile bekommen? Was ist, wenn Sie sich für einen Teil in der Mitte der Zeile interessieren, sich aus dem Rest aber überhaupt nichts machen?
  Normalerweise erhalten Sie Ihre Daten in irgendeiner rohen Form, aus der Sie die 
interessanten Dinge selbst »herauspicken« und speichern müssen. Dafür stellt Perl 
Ihnen die Funktion split zur Verfügung, die einen gegebenen String nach Ihren 
Vorgaben in eine Liste von Teilstrings aufteilt (splittet).
  Ihre Vorgaben sind dabei insbesondere die Zeichen oder Zeichenfolgen, bei denen 
split sozusagen »die Schere ansetzen« und den String aufteilen soll. Sie können hier 
(mit regulären Ausdrücken) die raffiniertesten Suchmuster festlegen. Heute allerdings 
betrachten wir nur das einfachste aller Trennzeichen: das Leerzeichen, das nicht nur 
bei Leerzeichen, sondern allen Leerstellen (auch Whitespace, »weißer Raum« genannt 
und als Sammelbegriff für Leerzeichen, Tabulator, Zeilenvorschub, Wagenrücklauf, 
Seitenvorschub und vertikalen Tabulator gebraucht) trennt.
  Sie übergeben der split-Funktion zwei Strings als Argumente - der erste enthält das 
Trennzeichen, und der zweite ist der String, den Sie bei jedem Vorkommen des 
Trennzeichens splitten möchten. Die split-Funktion teilt den String entsprechend auf 
und gibt Ihnen eine Liste der Teilstrings zurück, die Sie beispielsweise einer 
Arrayvariablen zuweisen und dann weiterarbeiten können. Der folgende Perl-Code 
zum Beispiel zerlegt die Zahlenfolge im String $zahlenfolge in ein Array von Zahlen:
$zahlenfolge = '34 23 56 34 78 38 90';
@zahlen = split(' ', $zahlenfolge);
  So! Jetzt können Sie mit den Zahlen im Array @zahlen nach Belieben herumspielen.
  Zum Abschluß dieser Lektion wollen wir Hashes und split zusammen in einem 
kleinen Beispiel einsetzen, das eine Namensliste einliest, diese Namen in einen nach 
Nachnamen aufgeschlüsselten Hash packt und sie dann mit dem Nachnamen zuerst 
und in alphabetischer Reihenfolge ausgibt. Das Ganze könnte zum Beispiel so 
aussehen:
Geben Sie einen Namen ein (Vor- und Nachname): Umberto Eco
Geben Sie einen Namen ein (Vor- und Nachname): Kurt Vonnegut
Geben Sie einen Namen ein (Vor- und Nachname): Fjodor Dostojewski
Geben Sie einen Namen ein (Vor- und Nachname): Albert Camus
Geben Sie einen Namen ein (Vor- und Nachname): Paul Auster
Geben Sie einen Namen ein (Vor- und Nachname): George Orwell
Geben Sie einen Namen ein (Vor- und Nachname):
Auster, Paul
Camus, Albert
Dostojewski, Fjodor
Eco, Umberto
Orwell, George
Vonnegut, Kurt
Listing 5.2 zeigt das zugehörige Skript:
Listing 5.2: Das Skript namen.pl
1: #!/usr/bin/perl -w
2:
3: $in = ''; # temp Input
4: %names = (); # Hash Namen
5: $fn = ''; # temp Vorname
6: $ln = ''; # temp Nachname
7:
8: while () {
9: print 'Geben Sie einen Namen ein (Vor- und Nachname): ';
10: chomp($in = <STDIN>);
11: if ($in ne '') {
12: ($fn, $ln) = split(' ', $in);
13: $names{$ln} = $fn;
14: }
15: else { last; }
16: }
17:
18: foreach $lastname (sort keys %names) {
19: print "$lastname, $names{$lastname}\n";
20: }
  Dieses Skript besteht aus drei Abschnitten: die Variablen initialisieren, die Daten 
einlesen und sie sortiert wieder ausgeben. Der Initialisierungsteil sollte mittlerweile klar 
sein, aber vielleicht fragen Sie sich, was das temp in den Kommentaren bedeutet. Es 
steht für temporär. Wie Sie wissen, dient der gesamte Initialisierungsabschnitt im 
Grunde nur der Übersichtlichkeit. Mit einem kurzen temp im Kommentar möchte ich 
hier auf den ersten Blick klarmachen, dass diese Variablen nur Zwischenspeicher für 
die Vor- und Nachnamen sind - die letztlich ja im Hash %names landen.
  In Zeile 8 bis 16 lesen wir die Daten ein, und zwar wie Sie es bereits aus dem 
Statistikskript kennen - mit einer while-Schleife, einem if zum Überprüfen auf eine 
Leerzeile und <STDIN> in skalarem Kontext. Anders als im Statistikskript packen wir 
die eingegebenen Strings hier nicht in ein Array, sondern splitten sie in Zeile 12 in 
zwei temporäre Skalarvariablen, $fn und $ln, auf. In Zeile 13 fügen wir die Inhalte 
dieser beiden Variablen dem Hash %names hinzu, den Nachnamen als Schlüssel und 
den Vornamen als Wert.
  Wenn alle Daten eingegeben sind, ist unser Hash komplett, und wir können ihn 
ausgeben. Auch diese Syntax haben Sie bereits gesehen, zuletzt in dem 
Histogrammbeispiel weiter oben in dieser Lektion. Diesmal sortieren wir die Schlüssel 
aber in alphabetischer Reihenfolge, deswegen genügt hier die einfachere Form von 
sort. Die print-Anweisung in Zeile 19 verwendet schließlich die Variable $lastname 
(die bei jedem foreach-Durchlauf den aktuellen Schlüssel enthält), um den 
Nachnamen und den diesem Schlüssel im Hash zugeordneten Vornamen auszugeben.
  Wenn die Schleifen Sie verwirren, versuchen Sie nur die anderen Teile der Beispiele 
zu verstehen. Morgen, am Tag 6, befassen wir uns ausführlich mit while und foreach. 
Dann erfahren Sie ganz genau, was die Schleifen in den Beispielen eigentlich 
machen.
  Wie Arrays sind auch Hashes Listen; deswegen brauchen wir in dieser Lektion 
eigentlich nicht viel tiefer zu gehen. Doch eine Funktion, die für den Gebrauch mit 
Hashes nützlich sein kann, möchte ich hier erwähnen: each.
  Die Funktion keys nimmt einen Hash als Argument entgegen und gibt eine Liste der 
Schlüssel im Hash zurück; values macht das gleiche mit den Hash-Werten. Übergibt 
man der Funktion each einen Hash als Argument, gibt sie ein Schlüssel/Wert-Paar als 
zweielementige Liste zurück: Das erste Element ist ein Key und das zweite der Wert. 
Das Besondere ist, dass Sie mit mehrmaligem Aufrufen von each den gesamten Hash 
durchgehen können. Wie bei allen Hash-Elementen ist die Reihenfolge der Paare 
nicht geordnet. Wenn each alle Elemente aus dem Hash gelesen hat, gibt es eine leere 
Liste () zurück.
  Heute haben wir mit der Behandlung der Hashes Ihr Grundwissen über Listendaten 
vervollständigt. Hashes sind Arrays und Listen sehr ähnlich - mit der Ausnahme, dass 
sie Daten in Schlüssel/Wert-Paaren anordnen, anstatt sie in einer numerischen 
Reihenfolge zu speichern. Wir haben besprochen, wie man einen Hash in einer Hash-
Variablen %hash speichert, auf einen Wert mit $hash{'key'} zugreift, Schlüssel aus 
dem Hash löscht und mit einer foreach-Schleife und der keys-Funktion alle Hash-
Elemente durchgehen kann.
Im folgenden noch einmal die Perl-Funktionen, die Sie heute kennengelernt haben:
exists nimmt einen Hash-Schlüssel entgegen und gibt wahr zurück, wenn dieser 
im Hash als Schlüssel vorhanden ist (auch wenn der diesem Schlüssel zugeordnete 
Wert undefiniert ist).
   
 delete nimmt einen Hash-Schlüssel entgegen und löscht diesen Schlüssel samt 
Wert aus dem Hash. Anders als undef, das einem Element in einem Hash oder 
Array den undefinierten Wert zuweist, entfernt delete das gesamte Paar 
(Schlüssel und Wert).
   
 keys nimmt einen Hash entgegen und gibt eine Liste aller Schlüssel in diesem 
Hash zurück.
   
 values nimmt einen Hash entgegen und gibt eine Liste aller Werte in diesem 
Hash zurück.
   
 split nimmt zwei Strings entgegen und splittet den zweiten String an den Stellen, 
an denen das im ersten String angegebene Trennzeichen auftaucht, in eine Liste 
von Teilstrings. Mit einem dritten Argument, einer Zahl, kann man festlegen, wie 
viele Elemente die von split erzeugte Liste höchstens enthalten darf.
   
Mehr Informationen finden Sie in der perlfunc-Manpage bzw. im Anhang A.
Frage:
 Diese verschiedenen Variablenzeichen! Wie soll ich die denn auseinanderhalten!
Antwort:
 Je öfter Sie sie benutzen, desto einfacher wird es auch, sich zu merken, 
welches wofür verwendet wird. Vielleicht hilft es, beim 
Skalarvariablenzeichen $ an den Buchstaben S wie Skalar zu denken (oder 
wenn Sie in $ nur Dollars sehen - Dollars sind Zahlen und skalar). Das at-
Zeichen @ sieht ein bißchen aus wie ein kleines a. A steht für Array. Und das 
Prozentzeichen % für Hashes besteht aus einem Schrägstrich mit zwei 
Punkten - einem für den Schlüssel und einem für den Wert. Wenn Sie auf 
Arrays oder Hashes zugreifen, denken Sie daran, was Sie haben möchten: 
Wollen Sie ein einzelnes Element, nehmen Sie $. Wollen Sie mehrere (eine 
Liste), verwenden Sie @.
Frage:
 Hashes sind bloß assoziative Arrays, oder nicht? Es sind doch nicht wirklich Hash-
Tabellen?
Antwort:
 Doch. »Assoziatives Array« ist eine andere, in früheren Perl-Versionen sogar 
»offizielle« Bezeichnung für einen Hash (»Hash« hat sich durchgesetzt, weil es 
sich bequemer aussprechen und tippen läßt als »assoziatives Array«, 
zumindest fanden das die Perl-Programmierer). Hashes sind intern aber auch 
tatsächlich als echte Hash-Tabellen implementiert und haben insbesondere 
bei riesigen Datenmengen alle Geschwindigkeitsvorteile eines Hash-
Verfahrens gegenüber einem auf Schlüsselvergleich basierenden Verfahren.
Frage:
 Sie verwenden in allen Beispielen einen Hash-Key, um auf einen Wert 
zuzugreifen. Gibt es auch einen Weg, mit einem Wert an einen Schlüssel zu 
kommen?
Antwort:
 Nein. Also, es gibt keine Funktion, die das tut. Sie könnten mit einer foreach-
Schleife und den Hash-Schlüsseln  den Hash durchlaufen, auf einen 
bestimmten Wert überprüfen und so den entsprechenden Schlüssel 
herausfinden. Aber bedenken Sie, dass verschiedene Schlüssel durchaus den 
gleichen Wert haben können, die Beziehung von einem Wert zu seinem 
Schlüssel also nicht die gleiche ist wie die eines Schlüssels zu seinem Wert.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.
$foo
@foo
%foo
$foo{'key'}
%zeug = qw(1 eins 2 zwei 3 drei 4 vier);
@zahlen = %zeug
$foo = %zeug;
keys, values und each.
 split?
length, split und 
reverse).
 Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.
	 $foo ist eine Skalarvariable.
 	 @foo ist eine Arrayvariable.
 	 %foo ist eine Hash-Variable.
 	 $foo{'key'} ist der dem Schlüssel 'key' zugeordnete Wert im Hash %foo.
 %zeug erhält vier Schlüssel/Wert-Paare: '1'/'eins', '2'/'zwei', 
'3'/'drei' und '4'/'vier'. Die qw-Funktion setzt die Anführungszeichen vor 
und nach jedem Element.
 %zeug werden in eine Liste zerlegt und im Array 
@zahlen gespeichert (Schlüssel, Wert, Schlüssel, Wert und so weiter)
 $foo enthält einen Code, der den internen Zustand des Hash beschreibt.
 keys liefert Ihnen eine Liste aller Schlüssel im Hash, die Funktion 
values eine Liste aller Werte. Die Funktion each liefert eine Liste von je einem 
Schlüssel/Wert-Paar. Mit Hilfe aufeinanderfolgender Aufrufe von each können 
Sie alle Paare im Hash durchgehen.
 split splittet einen String in eine Liste von Teilstrings. Split wird im allgemeinen 
zum Einlesen von Daten verwendet, die man nicht direkt einer Variablen zuweisen 
kann, was häufig der Fall ist, wenn man sie aus Dateien liest.
while-Schleife durch 
folgenden Code ersetzen, der mit Hilfe von split die eingegebene Zahlenreihe in 
eine Liste einzelner Zahlen zerlegt, sie im Array @zahlen speichert und dann mit 
foreach durchläuft:
print 'Geben Sie Ihre Zahlen ein, alle in einer Zeile, ';
print "durch Leerzeichen getrennt: \n";
chomp ($input = <STDIN>);
@nums = split(' ', $input);
$count = @nums;
foreach $num (@nums) {
$freq{$num}++;
$sum += $num;
}
#!/usr/bin/perl -w
#
# Satz-Statistik
$in = '' ; # temp Input
@sent = (); # Satz
$words = 0; # Anzahl Woerter
@reversed = 90; # Satz rueckwaerts
print 'Geben Sie einen Satz ein: ';
chomp($in = <STDIN>);
print 'Anzahl der Zeichen: ';
print length $in;
@sent = split(' ', $in);
$words = @sent;
print "\nAnzahl der Woerter: $words\n";
@reversed = reverse @sent;
print "der Satz rueckwaerts: \n";
print "@reversed\n";
#!/usr/bin/perl -w
$in = ''; # temp. Eingabe
%names = (); # Hash: Namen
@raw = (); # temp: rohe Woerter
$fn = ''; # Vorname
while () {
print 'Geben Sie einen Namen ein (Vor- und Nachname): ';
chomp($in = <STDIN>);
if ($in ne '') {
@raw = split(' ', $in);
if ($#raw == 1) { # Normalfall: zwei Woerter
$names{$raw[1]} = $raw[0];
} else { # den Vornamen zusammensetzen
$fn = '';
$i = 0;
while($i < $#raw) {
$fn .= $raw[$i++] . ' ';
}
$names{$raw[$#raw]} = $fn;
}
}
else { last; }
}
foreach $lastname (sort keys %names) {
print "$lastname, $names{$lastname}\n";
}




