TOC PREV Übungen NEXT INDEX

5 Hashes

In diesem Kapitel lernen wir ein Merkmal von Perl kennen, durch das Perl sich hinter keiner der großen Programmiersprachen zu verstecken braucht - Hashes1. Selbst wenn Sie schon einige Jahre Erfahrung mit anderen mächtigen Sprachen haben, ist es möglich, daß Sie noch nie etwas von Hashes gehört haben - obwohl Hashes ein mächtiges und nützliches Feature sind. In Perl sind Hashes derart wichtig, daß sie in fast jedem Programm vorkommen.

Was ist ein Hash?

Ähnlich wie ein Array ist auch ein Hash eine Datenstruktur, die eine Anzahl von Werten enthalten und wieder ausgeben kann. Anstatt die Werte jedoch, wie beim Array üblich, über einen numerischen Index anzusprechen, werden beim Hash die Werte mit einem Namen angesprochen. Die Indizes (die wir bei Hashes Schlüssel nennen) sind hier also keine Zahlen, sondern können beliebige, einmalige Strings sein (siehe Abbildung 5-1).
Abbildung 5 -1: Schlüssel und Werte eines Hashs

Die Schlüssel sind Strings, d. h. anstatt etwa das Element Nummer 3 aus einem Array anzusprechen, greifen wir nun auf das Hashelement mit dem Namen wilma zu.

Die Schlüssel sind Strings - Sie können also einen beliebigen Ausdruck, der einen String erzeugt, als Schlüssel in einem Hash benutzen. Diese Strings dürfen jeweils nur einmal vorkommen. Genauso wie es nur ein Arrayelement mit dem Index 3 geben kann, gibt es auch nur ein Hashelement mit dem Schlüssel wilma.

Eine andere Art, sich einen Hash vorzustellen, wäre eine Kiste voller Daten, wobei an jedem Datenstück ein Zettel hängt. Sie können in die Kiste fassen und einen beliebigen Zettel herausziehen, um zu sehen, welches Datenstück daran hängt. Aber es gibt kein »erstes Element«, sondern nur eine ungeordnete Ansammlung. In einem Array starten wir mit dem Element 0, darauf folgt Element 1, dann 2 und so weiter. In einem Hash gibt es dagegen keine feste Reihenfolge, also auch kein erstes Element. Ein Hash ist einfach eine Sammlung von Schlüssel/Wert-Paaren.

Sowohl Schlüssel als auch Werte können beliebige skalare Werte sein. Hierbei werden die Schlüssel jedoch prinzipiell in Strings umgewandelt. Würden Sie also den numerischen Ausdruck 50/20 als Schlüssel benutzen,2 so würde dieser in den String "2.5" (drei Zeichen) umgewandelt werden (einer der Schlüssel im Diagramm aus Abbildung 5-1).

Wieder gilt die Regel, daß Perl uns keine unnötigen Grenzen auferlegt: Ein Hash kann eine beliebige Größe haben, von einem leeren Hash bis hin zu einem, dessen Schlüssel/Wert-Paare Ihren gesamten Hauptspeicher füllen.

Einige Hashimplementierungen (wie etwa das Original in der Sprache awk, von der Larry sich die Idee ausgeliehen hat) werden mit zunehmender Größe der Hashes immer langsamer. Perl ist für diesen Fall jedoch mit einem guten, skalierbaren Algorithmus ausgestattet.3 Hat ein Hash also nur drei Schlüssel/Wert-Paare, geht das Heraussuchen der Werte recht schnell. Hat der Hash drei Millionen Schlüssel, so sollte das Heraussuchen genauso schnell vor sich gehen. Haben Sie also keine Angst vor großen Hashes.

An dieser Stelle möchten wir noch einmal darauf hinweisen, daß Hashschlüssel im Gegensatz zu den Werten nicht doppelt vorkommen können. Die Werte können aus Zahlen, Strings, undef-Werten oder beliebigen Kombinationen4 daraus bestehen. Die Schlüssel müssen jedoch einmalige Strings sein.

Warum einen Hash benutzen?

Besonders wenn Sie nach einem langen und produktiven Programmierer-Leben zum erstenmal von Hashes hören, kann es sein, daß Sie sich fragen, wofür man so etwas überhaupt braucht. Der grundsätzliche Gedanke besteht darin, miteinander »verwandte« Datensätze zu benutzen. Hier haben wir einige typische Verwendungen von Hashes in Perl:

Vorname, Familienname
Der Vorname ist hier der Schlüssel, der Familienname (Nachname) der Wert. Dies setzt natürlich einmalige Vornamen voraus; gäbe es zwei Leute mit dem Namen Randal, würde dieses Beispiel nicht funktionieren. Mit diesem Hash können Sie einen beliebigen Vornamen angeben und so den Nachnamen dieser Person herausfinden. Benutzen Sie etwa den Schlüssel Tom, bekommen Sie den Wert Phoenix.
Rechnername, IP-Adresse
Vielleicht wissen Sie bereits, daß jeder Rechner im Internet sowohl einen Rechnernamen (wie etwa www.stonehenge.com) als auch eine numerische IP-Adresse (wie etwa 123.45.67.89) besitzt. Das liegt daran, daß Maschinen lieber mit Zahlen arbeiten, uns Menschen es aber leichter fällt, uns die Namen zu merken. Die Rechnernamen sind einmalige Strings und können daher als Schlüssel in einem Hash benutzt werden. Mit einem Hash wie diesem können Sie also einen Rechnernamen angeben und die dazugehörige IP-Adresse herausfinden.
IP-Adresse, Rechnername
Oder umgekehrt: Wir stellen uns eine IP-Adresse normalerweise als Zahl vor, wir können sie aber auch als einmaligen String darstellen und dadurch als Schlüssel in einem Hash verwenden. In diesem Hash können wir eine IP-Adresse angeben, um den dazugehörigen Rechnernamen herauszufinden. Dies ist nicht der gleiche Hash wie im vorigen Beispiel. Hashes funktionieren nur in einer Richtung: vom Schlüssel zum Wert; es gibt keine Methode, anhand eines Wertes den dazugehörigen Schlüssel herauszufinden! Es handelt sich in diesen zwei Beispielen also um ein Paar Hashes, eines zum Speichern der IP-Adressen und eines für die Rechnernamen. Wie wir gleich sehen werden, ist es recht einfach, eins in das andere umzuwandeln.
Ein Wort, Anzahl der Vorkommen dieses Wortes5
Hier geht es darum herauszufinden, wie oft ein bestimmtes Wort in einem Dokument vorkommt. Vielleicht erzeugen Sie einen Index für eine Anzahl von Dokumenten, so daß ein Benutzer, der nach Fred sucht, herausfinden kann, daß dieses Wort in einem bestimmten Dokument fünfmal vorkommt. In einem anderen Dokument taucht Fred vielleicht siebenmal auf. Wieder ein anderes Dokument erwähnt Fred womöglich überhaupt nicht. Hierdurch können Sie zum Beispiel ermitteln, nach welchem Dokument der Benutzer wahrscheinlich sucht. Jedesmal, wenn das Indizierungsprogramm in einem bestimmten Dokument eine Erwähnung von Fred findet, wird der Wert, der zum Schlüssel Fred gehört, um 1 erhöht. Hätten wir Fred in einem Dokument also bereits zweimal gesehen, so wäre der Wert nun 2 und wir müßten ihn nun auf 3 erhöhen. Hätten wir Fred bis jetzt noch gar nicht gesehen, so müßten wir den Standardwert undef jetzt in 1 ändern.
Benutzername, Anzahl der benutzten (verschwendeten) Blöcke auf der Festplatte
Systemadministratoren mögen dieses Beispiel: Die Benutzernamen auf einem System sind alle einmalige Strings. Daher lassen sie sich in einem Hash einsetzen, um Informationen über einen bestimmten Benutzer zu ermitteln.
Ausweisnummer, Name
Es gibt eine Menge Leute, die Peter Meier heißen, aber wir hoffen, daß jeder von ihnen eine einmalige Ausweisnummer besitzt. Die Nummer dient uns hier als einmaliger Schlüssel und der Name der Person als Wert.

Sie können sich einen Hash also auch als eine sehr einfache Datenbank vorstellen, in der für jeden Schlüssel jeweils immer ein bestimmtes Datenstück abgelegt wird. Wenn Ihre Aufgabenbeschreibung Wörter wie »Duplikate finden«, »Querverweis« oder »Lookup-Tabelle« enthält, stehen die Chancen nicht schlecht, daß ein Hash für die Implementierung geeignet ist.

Zugriff auf Hashelemente

Um auf ein Hashelement zuzugreifen, benutzen Sie die folgende Schreibweise:

 $hash{$schluessel}

Diese Syntax ähnelt der, die wir für den Zugriff auf Arrayelemente bereits kennengelernt haben. Hier benutzen wir anstelle der eckigen Klammern jedoch geschweifte, um den Schlüssel zu kennzeichnen.6 Der Ausdruck für den Schlüssel ist hierbei ein String und keine Zahl.

 $familien_name{"Fred"}   = "Feuerstein";
 $familien_name{"Barney"} = "Geroellheimer";

Abbildung 5-2 zeigt, wie die resultierenden Schlüssel/Wert-Paare nach der Zuweisung aussehen:
Abbildung 5 -2: Schlüssel/Wert-Paare nach der Zuweisung

Wir können also zum Beispiel den folgenden Code schreiben:

 foreach $person (qw< Barney Fred >) {
   print "Ich kenne $person $familien_name{$person}.\n";
 }

Der Name eines Hashs ist ebenfalls ein ganz normaler Perl-Identifier (er kann aus Buchstaben, Zahlen und Unterstrichen bestehen, darf aber nicht mit einer Zahl beginnen). Auch Hashes haben ihren eigenen Namensraum. Es gibt also keine Beziehung zwischen dem Hashelement $familien_name{"Fred"} und etwa der Subroutine &familien_ name. Es besteht selbstverständlich kein Grund dafür, alle Leute zu verwirren, indem wir allem den gleichen Namen geben. Perl würde es jedoch nichts ausmachen, wenn Sie einen Skalar mit dem Namen $familien_name und ein Arrayelement mit dem Namen $familien_name[5] nebeneinander benutzen würden. Wir Menschen müssen das gleiche tun wie Perl: Wir müssen nachsehen, welche Zeichenfolge vor dem Identifier verwendet wird, um herauszufinden, was sie bedeutet. Steht also vor dem Identifier ein Dollar-Zeichen und dahinter geschweifte Klammern, wird offenbar auf ein Hashelement zugegriffen.

Wenn Sie sich einen Namen für einen Hash ausdenken, kann es hilfreich sein, sich das Wort »von« zwischen dem Namen des Hashs und dem Schlüssel zu denken. Zum Beispiel »der familien_name von Fred ist Feuerstein«. Dadurch, daß wir den Hash also familien_name nennen, wird klar, welche Beziehung zwischen den Schlüsseln und ihren Werten besteht.

Selbstverständlich können Sie für Hashschlüssel anstelle eines literalen Strings oder einfacher skalarer Variablen auch einen beliebigen Ausdruck benutzen, wie hier gezeigt:

 $foo = "Bar";
 print $familien_name{ $foo . "ney" };  # Gibt "Geroellheimer" aus

Wenn Sie versuchen, etwas in einem bereits existierenden Hashelement zu speichern, so wird der vorige Wert dadurch überschrieben:

 $familien_name{"Fred"} = "Astaire";  
 # Gibt einem bereits existierenden Element einen neuen Wert
 
 $steintal = $familien_name{"Fred"};
 # Zuweisung von "Astaire"; der alte Wert geht verloren

Dies funktioniert analog zu Arrays und Skalaren; wenn Sie in $pebbles[17] oder $dino einen neuen Wert ablegen, wird der alte überschrieben. Das gleiche passiert, wenn Sie etwas Neues in $familien_name{"Fred"} speichern - auch hier wird der alte Wert überschrieben.

Hashelemente können ebenfalls durch eine Zuweisung erzeugt werden:

 $familien_name{"Wilma"} = "Feuerstein";
 # fügt einen neuen Schlüssel (und einen neuen Wert) hinzu
 
 $familien_name{"Betty"} .= $familien_name{"Barney"};
 # erzeugt das Element, falls nötig

Dies geschieht analog zu Arrays und Skalaren; existieren $pebbles[17] oder $dino nicht bereits, so werden sie durch eine einfache Zuweisung erzeugt. Falls das Element $familien_name{"Betty"} nicht bereits existierte, so tut es das jetzt.

Und auch hier gibt der Zugriff auf nicht-existente Elemente undef zurück:

 $granit = $familien_name{"Larry"};  
 # Kein Larry gefunden, also: undef

Auch hier greift die Analogie: Enthalten $pebbles[17] oder $dino beim Zugriff auf sie keinen Wert, so wird undef zurückgegeben. Enthält $familien_name{"Larry"} beim Zugriff keinen Wert, so wird auch hier undef zurückgegeben.

Der Hash als Ganzes

Um einen Hash als Ganzes anzusprechen, benutzen Sie das Prozentzeichen als Präfix. Der Hash, den wir während der letzten Seiten benutzt haben, heißt also eigentlich %familien_name.

Aus Gründen der Bequemlichkeit ist es möglich, einen Hash in eine Liste und zurück zu konvertieren. Zuweisungen an einen Hash (in diesem Fall an den Hash aus Abbildung 5-1) geschehen als Zuweisung im Listenkontext, bei der Schlüssel und Werte die Listenelemente darstellen.7

 %irgendein_hash = ("foo", 35, "bar", 12.4, 2.5, "Hallo",
       "Wilma", 1.72e30, "Betty", "Tschüß\n");

Der Wert des Hashs (im Listenkontext) ist eine einfache Liste aus Schlüssel/Wert-Paaren:

 @irgendein_array = %irgendein_hash;

Dieses Vorgehen bezeichnen wir als Unwinding (engl. abwickeln) eines Hashes, wodurch eine Liste aus Schlüssel/Wert-Paaren entsteht. Die Paare befinden sich hierbei nicht unbedingt in der gleichen Reihenfolge wie in der ursprünglichen Liste:

 print "@irgendein_array\n";
   # könnte etwas wie das Folgende ergeben:
   # Betty Tschüß (und ein Newline-Zeichen) Wilma
   # 1.72e+30 foo 35 2.5 Hallo bar 12.4

Die Reihenfolge ist vermutlich eine andere, da Perl die Schlüssel/Wert-Paare intern so ablegt, daß sie sich schnell nachschlagen lassen. Sie benutzen einen Hash also normalerweise in Situationen, in denen es nicht wichtig ist, in welcher Reihenfolge sich die Einträge befinden, oder wenn Sie eine einfache Möglichkeit besitzen, die von Ihnen benötigte Reihenfolge herzustellen.

Trotzdem bleibt jeder Schlüssel in der neuen Liste mit dem dazugehörigen Wert verbunden. Das heißt: Selbst wenn Sie nicht wissen, an welcher Stelle der Liste der Schlüssel foo auftaucht, können Sie sichergehen, daß der dazugehörige Wert, nämlich 35, direkt auf seinen Schlüssel folgt.

Hashzuweisung

Auch wenn es selten gebraucht wird, kann ein Hash kopiert werden, indem Sie die folgende (leicht nachvollziehbare) Schreibweise verwenden:

 %neuer_hash = %alter_hash;

Hierfür muß Perl einen größeren Aufwand treiben, als es zunächst scheint. Im Gegensatz zu Sprachen wie Pascal oder C, bei denen einfach ein bestimmter Speicherbereich kopiert wird, sind die Datenstrukturen von Perl komplexer aufgebaut. Zuerst wird Perl durch diese Codezeile angewiesen, %alter_hash in eine Liste von Schlüssel/Wert-Paaren umzuwandeln und diese dann Paar für Paar %neuer_hash zuzuweisen.

Üblicher ist es, den Hash auf eine bestimmte Art umzuwandeln. Zum Beispiel könnten wir den Hash umkehren:

 %umgedrehter_hash = reverse %irgendein_hash;

Mit dieser Zeile wird %irgendein_hash in eine Liste aus Schlüssel/Wert-Paaren »abgespult«, wodurch eine Liste in der Form (Schlüssel, Wert, Schlüssel, Wert, Schlüssel, Wert, ...) entsteht. Der reverse-Operator dreht nun diese Liste um, wodurch wir nun eine Liste der Form (Wert, Schlüssel, Wert, Schlüssel, Wert, Schlüssel, ...) bekommen. Jetzt stehen die Werte anstelle der Schlüssel und die Schlüssel an der Stelle der Werte. Weisen wir diese Liste nun %umgedrehter_hash zu, können wir nun einen String, der vorher ein Wert in %irgendein_hash war, als Schlüssel benutzen. Jetzt haben wir also eine Methode, um anhand eines »Wertes« (der jetzt ein »Schlüssel« ist) den dazugehörigen »Schlüssel« (jetzt ein »Wert«) zu ermitteln.

Wenn Sie aufmerksam gelesen haben, wissen Sie bereits, daß diese Methode nur dann korrekt funktioniert, wenn auch die Werte im ursprünglichen Hash nur einmal vorkamen, da es keine doppelten Schlüssel geben darf. Perl geht hierbei nach folgender Regel vor: Der Letzte gewinnt. Das bedeutet, ein bereits angelegter Schlüssel kann von einem später erzeugten überschrieben werden. Da wir allerdings nicht wissen, in welcher Reihenfolge die Paare in die Liste »abgespult« werden, läßt sich auch nicht sagen, wer das Rennen macht. Sie sollten diese Technik also nur dann anwenden, wenn Sie wissen, daß es unter den ursprünglichen Werten keine Duplikate gibt.8 In unseren Beispielen mit den Rechnernamen und IP-Adressen ist dies der Fall.

 %ip_addresse = reverse %rechner_name;

Jetzt können wir sowohl den Rechnernamen als auch die IP-Adresse auf die gleiche leichte Art nachschlagen.

Der große Pfeil

Wenn Sie Zuweisungen auf einen Hash vornehmen, ist es manchmal nicht ganz offensichtlich, welche Elemente nun die Schlüssel sind und welche die Werte. In dieser Zuweisung müssen wir Menschen beispielsweise die Liste durchzählen und dabei sagen »Schlüssel, Wert, Schlüssel, Wert, ...« , um herauszufinden, ob etwa 2.5 nun ein Schlüssel oder ein Wert ist:

 %irgendein_hash = ("foo", 35, "bar", 12.4, 2.5, "Hallo",
       "Wilma", 1.72e30, "Betty", "Tschüß\n");

Wäre es nicht nett, wenn Perl uns die Möglichkeit gäbe, Schlüssel und Werte in einer solchen Liste auch als Paare darzustellen? Das hat sich Larry auch gedacht, weshalb er auch den großen Pfeil (=>) erfunden hat.9 Für Perl ist dies nur eine weitere Möglichkeit, ein Komma zu schreiben. Das heißt, immer wenn Sie in Perl ein Komma ( , ) verwenden, können Sie auch den großen Pfeil benutzen; für Perl macht das keinen Unterschied.10 Eine andere Art, unseren Hash von Familiennamen zu schreiben, sieht so aus:

 my %familien_name = (  # ein Hash kann als lexikalische Variable # definiert werden
   "Fred"   => "Feuerstein",
   "Dino"   => undef,
   "Barney" => "Geroellheimer",
   "Betty"  => "Geroellheimer",
 );

Jetzt haben wir eine leichter lesbare Methode, um zu sehen, was ein Schlüssel ist und was ein Wert, selbst wenn alle Paare auf einer Zeile stehen. Beachten Sie auch das zusätzliche Komma am Ende der Liste. Wie wir bereits gesehen haben, ist dieses Verfahren harmlos, aber dafür bequem. Müssen wir dem Hash noch weitere Personen hinzufügen, ist nur darauf zu achten, daß auf jeder Zeile ein Schlüssel/Wert-Paar und ein nachgestelltes Komma steht. Perl faßt das so auf, als stünde zwischen jedem Eintrag in dieser Liste ein Komma und ein weiteres (harmloses) Komma am Ende der Liste.

Hashfunktionen

Natürlich gibt es auch eine ganze Reihe von nützlichen Funktionen, die Sie auf einen Hash als Ganzes anwenden können.

Die Funktionen keys und values

Die Funktion keys gibt eine Liste aller in einem Hash enthaltenen Schlüssel zurück, während values die dazugehörigen Werte ausgibt. Enthält der Hash keine Elemente, so wird von beiden Funktionen eine leere Liste zurückgegeben.

 my %hash = ("a" => 1, "b" => 2, "c" => 3);
 my @schluessel = keys   %hash;
 my @werte      = values %hash;

Das Array @schluessel enthält folglich die Liste ("a", "b", "c"), während @werte (1, 2, 3) enthält - in irgendeiner Reihenfolge. Denken Sie daran: Perl speichert die Paare eines Hashs in keiner bestimmten Ordnung: War "b" der letzte Schlüssel, so ist 2 der letzte Wert; ist "c" der erste Schlüssel, so ist 3 der erste Wert. Diese Aussage ist wahr, solange Sie den Hash nicht zwischen den Aufrufen von keys und values verändern. Wenn Sie zwischen den Aufrufen beispielsweise neue Elemente hinzufügen, so behält Perl sich das Recht vor, die Elemente intern in eine neue Reihenfolge zu bringen, damit der Zugriff immer so schnell wie möglich erfolgen kann.11

In skalarem Kontext geben diese Funktionen die Anzahl der Elemente (Schlüssel/Wert-Paare) in dem Hash zurück. Dies geschieht sehr effizient, ohne daß dabei jedes Element in dem Hash einzeln betrachtet werden muß:

 my $anzahl = keys %hash;  # ergibt 3, also drei Schlüssel/Wert-Paare

Sehr selten werden Sie sehen, daß jemand einen Hash in einem Booleschen Ausdruck (wahr/falsch) benutzt, etwa so:

 if (%hash) {
   print "Das ist wahr!\n";
 }

Dieser Ausdruck ist nur (und nur dann) wahr, wenn der getestete Hash mindestens ein Schlüssel/Wert-Paar enthält.12 Es ist also nur eine andere Art zu sagen: »Wenn der Hash nicht leer ist...«. Aber diese Verwendungsweise ist, wie sich gezeigt hat, eher selten.

Die Funktion each

Eine übliche Methode, über einen vollständigen Hash zu iterieren (soll heißen, jedes Element einzeln zu betrachten), besteht darin, die each-Funktion zu verwenden. Diese gibt pro Iteration ein Schlüssel/Wert-Paar als Liste aus zwei Elementen zurück.13 Jedesmal wenn diese Funktion für denselben Hash aufgerufen wird, wird das nächste Schlüssel/Wert-Paar zurückgegeben, bis alle Elemente einmal gelesen wurden. Sind keine Elemente mehr übrig, gibt each eine leere Liste zurück.

In der Praxis wird each ausschließlich innerhalb einer while-Schleife benutzt. Zum Beispiel:

 while ( ($schluessel, $wert) = each %hash ) {
   print "$schluessel => $wert\n";
 }

Hier passiert mehr, als es auf den ersten Blick scheint. Als erstes gibt each %hash ein Schlüssel/Wert-Paar aus dem Hash in Form einer Liste aus zwei Elementen zurück. Der Schlüssel soll hier "c" sein und der Wert 3. In diesem Fall lautet die Liste ("c", 3). Die Elemente dieser Liste werden nun zwei skalaren Variablen in der Liste ($schluessel, $wert) zugewiesen. $schluessel enthält also jetzt "c" und $wert enthält 3.

Diese Listenzuweisung findet ihrerseits im Bedingungsteil der while-Schleife statt. Für diesen Teil als Ganzes gilt ein skalarer Kontext. (Um genau zu sein, handelt es sich um einen Booleschen Kontext, bei dem nach einem wahren bzw. falschen Wert gesucht wird.) Der Wert einer Listenzuweisung in skalarem Kontext ist die Anzahl der Elemente der ursprünglichen Liste, also 2. Da 2 ein wahrer Wert ist, begeben wir uns in den Schleifenkörper und geben die Nachricht c => 3 aus.

Für den nächsten Schleifendurchlauf versorgt uns each %hash mit einem neuen Schlüssel/Wert-Paar, sagen wir ("a", 1). (Die each-Funktion gibt uns jedesmal ein neues Paar aus, da sie sich »merkt«, welche Paare wir bereits gesehen haben. Technisch gesprochen sagen wir, jeder Hash besitzt einen eigenen Iterator.14) Dieses Paar wird nun seinerseits in ($schluessel, $wert) gespeichert. Da die Anzahl der Elemente auch dieses Mal 2 war, ist die Bedingung für die Schleife ein weiteres Mal erfüllt und der Anweisungsblock gibt dieses Mal a => 1 aus.

Beim dritten Schleifendurchlauf wissen wir bereits, was uns erwartet, und folglich ist es auch keine Überraschung, die Ausgabe b => 2 zu sehen.

Das kann natürlich nicht ewig so weitergehen. Beim vierten Versuch, each %hash auszuwerten, findet Perl keine weiteren Schlüssel/Wert-Paare mehr, und each muß folglich eine leere Liste zurückgeben.15 Diese leere Liste wird ($schluessel, $wert) zugewiesen, wodurch beide Variablen nun undef als Wert enthalten.

Das ist aber nicht so wichtig, da das ganze Konstrukt im Bedingungsteil der while-Schleife ausgewertet wird. Wie gesagt: Der Wert einer Listenzuweisung im skalaren Kontext besteht aus der Anzahl der ursprünglichen (von each zurückgegebenen) Listenelemente. In diesem Fall ist das 0. Da 0 ein falscher Wert ist, wird hier die while-Schleife beendet und das Programm geht weiter.

Selbstverständlich gibt each die Schlüssel/Wert-Paare in ungeordneter Reihenfolge zurück. (Dies ist zufällig auch die gleiche Reihenfolge, die auch keys und values ergeben, nämlich die »interne« Reihenfolge des Hashs.) Wenn Sie den Hash in einer bestimmten Reihenfolge abarbeiten müssen, sortieren Sie einfach die Schlüssel. Zum Beispiel:

 foreach $schluessel (sort keys %hash) {
   $wert = $hash{$schluessel};
   print "$schluessel => $wert\n";
   # Wir hätten auch auf die zusätzliche Variable $schluessel 
   # verzichten können:
   # print "$schluessel => $hash{$schluessel}\n";
 }

Weitere Möglichkeiten, einen Hash zu sortieren, finden Sie in Kapitel 15.

Typische Anwendung für einen Hash

An dieser Stelle finden Sie es vermutlich hilfreich, ein etwas konkreteres Beispiel zu sehen.

Die Bibliothek von Steintal benutzt ein Perl-Programm, das (neben anderen Informationen) mit Hilfe von Hashes ermittelt, wie viele Bücher eine bestimmte Person ausgeliehen hat.

 $buecher{"Fred"}  = 3;
 $buecher{"Wilma"} = 1;

Herauszufinden, ob ein Element des Hashs wahr oder falsch ist, ist ganz einfach:

 if ($buecher{$jemand}) {
   print "$jemand hat mindestens ein Buch ausgeliehen.\n";
 }

Es gibt aber noch weitere Elemente in dem Hash, die falsch sein können:

 $buecher{"Barney"}  = 0;      # im Moment keine Bücher ausgeliehen
 $buecher{"Pebbles"} = undef;  # Noch kein Buch ausgeliehen - neuer
                               # Benutzerausweis

Da Pebbles bisher noch kein Buch ausgeliehen hat, besitzt ihr Eintrag den Wert undef anstelle von 0.

Für jeden vergebenen Benutzerausweis gibt es einen Schlüssel in dem Hash. Für jeden Schlüssel (also für jeden Benutzer) existiert ein Wert, der die Anzahl der ausgeliehenen Bücher repräsentiert oder undef, falls dieser Benutzer seinen Ausweis bis jetzt noch nicht gebraucht hat.

Die exists-Funktion

Um herauszufinden, ob ein bestimmter Schlüssel in dem Hash existiert (also, ob jemand einen Benutzerausweis hat oder nicht), können Sie die Funktion exists benutzen. Diese gibt einen wahren Wert zurück, wenn der angegebene Schlüssel im Hash existiert, und falsch, wenn nicht:

 if (exists $buecher{"Dino"}) {
   print "Na sowas, Dino hat einen Benutzerausweis!\n";
 }

Man kann also sagen, exists $buecher{"Dino"} gibt nur dann einen wahren Wert zurück, wenn Dino auch tatsächlich in der Liste der Schlüssel von %buecher gefunden wird.

Die delete-Funktion

Die Funktion delete entfernt den angegebenen Schlüssel (und den dazugehörigen Wert) aus einem Hash. (Gibt es keinen solchen Schlüssel, gibt es auch nichts zu tun. Es wird in diesem Fall keine Warnung ausgegeben.)

 my $person = "Betty";
 delete $buecher{$person};  # Benutzerausweis von $person einziehen

Dies ist nicht das gleiche, wie dem Hashelement undef zuzuweisen - eigentlich ist das sogar genau das Gegenteil! Die Auswertung von exists $buecher{"Betty"} ergibt in beiden Fällen gegenteilige Ergebnisse. Nachdem Sie mit delete einen Schlüssel aus einem Hash entfernt haben, kann sich dieser nicht mehr im Hash befinden. Weisen Sie dem Schlüssel jedoch undef als Wert zu, muß der Schlüssel existieren.

Interpolation von Hashelementen

Sie können ein einzelnes Hashelement in einem String in doppelten Anführungszeichen genauso interpolieren wie andere Variablen auch:

 foreach $person (sort keys %buecher) { 
 # für jeden Benutzer der Bibliothek in sortierter Reihenfolge
   if ($buecher{$person}) {
     print "$person hat $buecher{$person} Buecher ausgeliehen.\n";
     # Fred hat 3 Bücher ausgeliehen
   }
 }

Ganze Hashes können jedoch nicht interpoliert werden; "%buecher" innerhalb eines Strings bedeutet also einfach nur die (literalen) Zeichen %buecher.16 Jetzt haben wir sämtliche magischen Sonderzeichen kennengelernt, die in Strings mit doppelten Anführungszeichen durch einen Backslash geschützt werden müssen: $ und @, da sie eine Variable einleiten, die interpoliert werden soll; ", da dies das Quoting-Zeichen ist, das ohne Backslash den String vorzeitig beenden würde, und schließlich \, den Backslash selbst. Alle anderen Zeichen innerhalb von Strings in doppelten Anführungszeichen sind nicht magisch und stehen einfach für sich selbst.17

Übungen

Die Lösungen zu den folgenden Übungen finden Sie in Anhang A:

  1. [7] Schreiben Sie ein Programm, das den Benutzer auffordert, einen Namen einzugeben, und dann den richtigen Familiennamen ausgibt. Benutzen Sie Namen Ihrer Freunde, oder (wenn Sie so viel Zeit am Computer sitzen, daß Sie keine realen Leute mehr kennen) benutzen Sie die untenstehende Tabelle:
    Eingabe
    Ausgabe
    Fred
    Feuerstein
    Barney
    Geroellheimer
    Wilma
    Feuerstein
    Zur Lösung
  2. [15] Schreiben Sie ein Programm, das eine Reihe von Wörtern (jeweils auf einer eigenen Zeile18) bis zum Dateiende-Zeichen einliest und dann eine Zusammenfassung ausgibt, wie oft jedes Wort eingegeben wurde. (Tip: Denken Sie daran, daß ein undefinierter Wert, der als Zahl benutzt wird, von Perl automatisch in eine 0 umgewandelt wird. Es könnte helfen, wenn Sie sich die frühere Übung noch einmal ansehen, bei der eine Gesamtsumme ermittelt wurde.) Wären die eingegebenen Wörter also Fred, Barney, Fred, Dino, Wilma, Fred (jeweils auf einer eigenen Zeile) gewesen, sollte uns die Ausgabe anzeigen, daß Fred dreimal eingegeben wurde. Zusatzpunkte gibt es, wenn Sie das Ergebnis außerdem noch in ASCII-Reihenfolge ausgeben.
    Zur Lösung

Fußnoten

1

Früher nannten wir diesen Datentyp »assoziative Arrays«. Aber die Perl-Gemeinschaft hat ungefähr 1995 entschieden, daß das zu viele Buchstaben zu schreiben und zu viele Silben zu sprechen seien, und den Namen in »Hashes« geändert.

2

Dies ist ein numerischer Ausdruck und nicht etwa der aus fünf Zeichen bestehende String "50/20". Hätten wir den String als Schlüssel benutzt, so würde er selbstverständlich unverändert bleiben.

3

Technisch gesehen baut Perl die Hashtabelle, je nach der benötigten Hashgröße, neu auf. (Das Wort »Hash« kommt von der Tatsache, daß intern eine Hashtabelle zur Implementierung dieses Datentyps verwendet wird.)

4

Oder, um genau zu sein, vollkommen beliebige skalare Werte, also auch diejenigen, die wir bisher noch nicht behandelt haben.

5

Dies ist eine sehr häufige Verwendung für einen Hash! Sie kommt sogar so oft vor, daß sie eventuell in einer Übung am Ende des Kapitels auftauchen könnte!

6

Hier ein kleiner Ausflug in Larry Walls Denkweise: Larry sagt, daß wir hier geschweifte Klammern anstelle von eckigen benutzen, da wir hier etwas anderes tun, als einen einfachen Arrayzugriff vorzunehmen. Daher sollten wir auch eine andere Schreibweise benutzen.

7

Sie können sämtliche Listen-Operatoren und Ausdrücke benutzen. Die einzige Bedingung besteht darin, daß es sich um eine gerade Anzahl von Elementen handeln muß, da der Hash nun einmal aus Schlüssel/Wert-Paaren besteht. Eine ungerade Anzahl wird wahrscheinlich irgend etwas Unzuverlässiges tun, kann aber mit eingeschalteten Warnungen abgefangen werden.

8

Oder es ist Ihnen egal, ob es Duplikate gibt. So könnten wir beispielsweise den Hash %familien_name (in dem die Vornamen als Schlüssel verwendet werden und die Familiennamen als Werte) umkehren, um festzustellen, ob es innerhalb dieser Gruppe jemanden mit einem bestimmten Familiennamen gibt. Gibt es im umgedrehten Hash also keine Schlüssel Schiefer, wissen wir, daß im ursprünglichen Hash dieser Name nicht vorkommt.

9

Ja, es gibt auch einen kleinen Pfeil. Dieser wird für Referenzen benutzt, die als Thema für Fortgeschrittene gelten. Wenn Sie soweit sind, können Sie mehr darüber in den perlreftut- und perlref-Manpages erfahren.

10

Einen technischen Unterschied gibt es doch: Barewords (Folgen von Buchstaben, Ziffern und Unterstrichen, die nicht mit einer Ziffer beginnen dürfen) links vom Pfeil müssen nicht von Anführungszeichen umgeben werden. Dies geschieht implizit. Das gleiche gilt innerhalb geschweifter Klammern bei der Verwendung eines Barewords als Hashschlüssel.

11

Wenn Sie zwischen den Aufrufen von keys und values neue Elemente hinzugefügt hätten, würde Ihre Liste von Werten (oder Schlüsseln, je nachdem, was Sie zuerst getan haben) nun zusätzliche Elemente enthalten, die sich schwerlich mit der ersten Liste abgleichen ließen. Daher würde normalerweise auch kein Programmierer so etwas tun.

12

Das tatsächliche Ergebnis ist ein interner Debugging-String, der für die Leute, die Perl weiterentwickeln, wichtig ist. Es sieht etwa so aus: »4/16«. Es wird jedoch garantiert, daß der Wert wahr ist, sofern der Hash mindestens ein Paar enthält, und falsch, wenn der Hash leer ist. Der Rest von uns kann die Funktion also auch weiterhin dafür benutzen.

13

Bei einer anderen üblichen Methode, über den Hash zu iterieren, wird foreach auf eine Liste von Schlüsseln angewendet. Wir zeigen diese Methode am Ende dieses Abschnitts.

14

Da jeder Hash seinen eigenen, privaten Iterator besitzt, können Schleifen, die each benutzen, auch ineinander verschachtelt sein, solange über verschiedene Hashes iteriert wird. Und wo wir uns sowieso gerade in einer Fußnote befinden, können wir Ihnen auch gleich sagen, daß es sehr unwahrscheinlich ist, daß Sie das jemals tun müssen. Allerdings können Sie den Iterator eines Hashs mittels der keys- oder der values-Funktion wieder zurücksetzen. Dies geschieht, sobald eine neue Liste im Hash gespeichert wird oder wenn each nach dem Iterieren über alle Elemente am »Ende« des Hashs angekommen ist. Gleichzeitig ist das Hinzufügen von neuen Schlüssel/Wert-Paaren während des Iterierens normalerweise keine gute Idee, da hierbei der Iterator nicht unbedingt zurückgesetzt wird.

15

Es wird hier im Listenkontext benutzt, kann also auch kein undef zurückgeben, um einen Fehler anzuzeigen; das wäre nämlich eine Liste mit einem Element (undef) anstelle der leeren Liste(   ), die überhaupt kein Element enthält.

16

Den gesamten Hash hier als eine Folge von Schlüssel/Wert-Paaren ausgeben zu wollen, wäre nicht besonders nützlich. Außerdem hat das Prozentzeichen in den Formatanweisungen für printf eine andere Bedeutung (siehe Kapitel 6, I/O-Grundlagen). Ihm hier eine andere Bedeutung geben zu wollen wäre außerst unbequem.

17

Seien Sie aber bei der Benutzung eines Apostrophs ('), einer linken eckigen Klammer, einer linken geschweiften Klammer ({), dem kleinen Pfeil (->) und dem doppelten Doppelpunkt auf der Hut. Folgt eines dieser Zeichen direkt auf einen Variablennamen, so hat das vermutlich eine andere Bedeutung, als Sie denken.

18

Sie müssen die Wörter zeilenweise eingeben, da wir Ihnen bis jetzt noch nicht gezeigt haben, wie Sie einzelne Wörter aus einer Zeile extrahieren können.


TOC PREV Übungen NEXT INDEX

Copyright © 2002 by O'Reilly Verlag GmbH & Co.KG