You are here: Perldoc Web>PerlDokumentListe>Perldsc (2006-01-16)
perldsc Dokumentation zu Perl 5.8.0 | Download als POD | Wie kann ich hier etwas ändern?

NAME

perldsc - Kochbuch für Perldatenstrukturen

BESCHREIBUNG

Der gravierendste Fehlbestand in der Programmiersprache Perl vor dem Erscheinen der Version 5.0 waren komplexe Datenstrukturen. Selbst ohne direkte Unterstützung durch die Programmiersprache schafften es einige tapfere Programmierer, sie zu emulieren, aber es war harte Arbeit und nichts für Zartbesaitete. Gelegentlich konnte man sich mit der $m{$AvA,$b} Schreibweise, wie sie in awk verwendet wird, durchmogeln, in der die Schlüssel in Wirklichkeit mehr eine einzelne zusammengesetzte Zeichenkette "$AvA$b" sind, aber das Durchlaufen der Werte und die Sortierung waren schwierig. Verzweifeltere Programmierer hackten direkt die interne Symboltabelle von Perl, eine Strategie die sich -- gelinde gesagt -- als schwer zu entwickeln und zu verwalten herausstellte.

Die Version 5.0 von Perl bietet uns die Möglichkeit, komplexe Datenstrukturen zu verwenden. Man kann nun etwas wie das hier schreiben und auf ein mal hat man ein Array mit drei Dimensionen!

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            for $z (1 .. 10) {
                $AvA[$x][$y][$z] =
                    $x ** $y + $z;
            }
        }
    }

Leider verbirgt sich hinter alledem, wie einfach es auch erscheinen mag, ein viel ausgefeilteres Konstrukt als man oberflächlich erkennen kann!

Wie gibt man das aus? Warum kann man nicht einfach print @AvA schreiben? Wie sortiert man es? Wie kann man eines von diesen Dingern an eine Funktion übergeben oder als Rückgabewert von einer Funktion erhalten? Ist es ein Objekt? Kann man es auf der Festplatte speichern und später wieder einlesen? Wie greift man auf ganze Zeilen oder Spalten dieser Matrix zu? Müssen alle Werte numerisch sein?

Wie man sieht ist es leich, sich verwirren zu lassen. Während ein gewisser kleiner Teil der Schuld daran die auf Referenzen basierende Implementation trifft, so liegt es doch hauptsächlich an einem Mangel bestehender Dokumentation mit Beispielen, die für Anfänger entworfen wurden.

Dieses Dokument ist als detaillierte aber verständliche Abhandlung über die vielen verschiedenen Sorten von Datenstrukturen, die man vielleicht entwickeln möchte, gedacht. Es sollte auch als Kochbuch voller Beispiele dienen. Auf diese Art kann man, wenn man eine dieser komplexen Datenstrukturen herstellen muss, einfach ein passendes Beispiel von hier schnappen, stibitzen oder stehlen.

Lasst uns einen Blick im Detail auf jede dieser möglichen Konstruktionen werfen. Es gibt separate Sektionen über jede der folgenden:

* Arrays von Arrays

* Hashes von Arrays

* Arrays von Hashes

* Hashes von Hashes

* komplexere Strukturen
Aber für den Moment befassen wir uns erst einmal mit den Problemem, die all diese Typen von Datenstrukturen gemeinsam haben.

REFERENZEN

Die wichtigste Sache, die man über alle Datenstrukturen in Perl -- inklusive mehrdimensionaler Arrays -- verstehen muss, ist, dass selbst wenn sie anders aussehen mögen, Perls @ARRAY s und %HASH es intern alle eindimensional sind. Sie können nur skalare Werte speichern (das heißt eine Zeichenkette, eine Zahl oder eine Referenz). Sie können nicht direkt andere Arrays oder Hashes enthalten, sondern enthalten stattdessen Referenzen auf weitere Arrays oder Hashes.

Man kann eine Referenz auf ein Array oder einen Hash nicht ganz in der gleichen Art und Weise wie ein echtes Array oder einen echten Hash benutzen. Für C oder C++ Programmierer, die es nicht gewohnt sind, zwischen Arrays und Zeigern auf dieselben einen Unterschied zu machen, kann das verwirrend sein. Falls dem so ist, denke man sich den Unterschied so wie zwischen einer Struktur und einem Zeiger auf eine Struktur.

Du kannst (und solltest) mehr über Referenzen in perlref lesen. Kurz gefasst sind Referenzen so etwas wie Zeiger, die wissen worauf sie zeigen. (Objekte sind ebenfalls eine Art von Referenzen, aber wir benötigen sie nicht sofort -- falls überhaupt.) Das heißt, dass etwas, was für Dich wie ein Zugriff auf ein zwei- oder höherdimensionales Array und/oder einen Hash aussieht, in Wahrheit nur eine eindimensionale Einheit als Basistyp hat, die Referenzen auf die nächste Ebene enthält. Es ist nur so, dass Du es so verwenden kannst als wäre es eine zwei-dimensionale Struktur. Gewöhnlich ist das genau die Art, wie mehrdimensionale Arrays auch in C funktionieren.

    $array[7][12]                               # Array von Arrays
    $array[7]{Zeichenkette}                     # Array von Hashes
    $hash{Zeichenkette}[7]                      # Hash von Arrays
    $hash{Zeichenkette}{'andere Zeichenkette'}  # Hash von Hashes

Da nun die oberste Ebene nur Referenzen enthält, bekommt man, wenn man versucht sein Array mit einer einfachen print() Funktion auszugeben, etwas, was nicht sehr hübsch aussieht, so wie das hier:

    @AvA = ( [2, 3], [4, 5, 7], [0] );
    print $AvA[1][2];
  7
    print @AvA;
  ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0)

Das liegt daran, dass Perl niemals automatisch Deine Variablen dereferenziert. Wenn Du zu dem Objekt gelangen willst, auf das eine Referenz zeigt, dann musst Du das selbst tun, indem Du entweder vorangestellte Typindikatoren, wie ${$blah} , @{$blah} oder @{$blah[$i]} , oder angehängte Pfeile, wie $a->[3] , $h->{fred} oder sogar $ob->method()->[3] , verwendest.

VERBREITETE FEHLER

Die beiden am weitesten verbreiteten Fehler, die bei der Konstruktion von etwas wie einem Array von Arrays gemacht werden, sind zum einen das versehentliche verwenden der Elementanzahl und zum anderen das mehrfache Referenzieren derselben Speicherstelle. Hier ist der Fall wo man statt eines verschachtelten Arrays einfach die Elementanzahl bekommt:

    for $i (1..10) {
        @array = irgendEineFunktion($i);
        $AvA[$i] = @array;      # FALSCH!
    }

Hier handelt es sich schlichtweg um den einfachen Fall, dass ein Array im Skalarkontext zugewiesen wird, was seine Elementanzahl liefert. Falls das das ist, was Du tatsächlich und wirklich haben willst, dann magst Du gut daran tun, es ein Stück ausdrücklicher zu sagen, so wie hier:

    for $i (1..10) {
        @array = irgendEineFunktion($i);
        $anzahl[$i] = scalar @array;
    }

Hier ist der Fall, in dem wieder und wieder eine Referenz auf dieselbe Speicherstelle genommen wird:

    for $i (1..10) {
        @array = somefunc($i);
        $AvA[$i] = \@array;     # WRONG!
    }

So, was ist jetzt das große Problem hiermit? Es sieht richtig aus, nicht wahr? Immerhin habe ich Dir gerade erzählt, dass Du ein Array von Referenzen brauchst, also hast Du mir, zum Donnerwetter, eines hergestellt!

Obwohl das stimmt, ist dieser Code unglücklicherweise trotzdem kaputt. Alle Referenzen in @AvA zeigen auf genau die gleiche Speicherstelle und enthalten daher alle das, was sich zuletzt in @array befand! Es ist ähnlich zu dem Problem, das in folgendem C Programm demonstriert wird:

    #include <pwd.h>
    main() {
        struct passwd *getpwnam(), *rp, *dp;
        rp = getpwnam("root");
        dp = getpwnam("daemon");

        printf("daemon hat den Namen %s\nroot hat den Namen %s\n",
                dp->pw_name, rp->pw_name);
    }

Was

    daemon hat den Namen daemon
    root hat den Namen daemon

ausgeben wird.

Das Problem ist, dass sowohl rp als auch dp Zeiger auf dieselbe Speicherstelle sind! In C müsstest Du daran denken selbst mittels malloc() irgendeinen neuen Speicherbereich zu reservieren. In Perl wirst du wohl den Arraykontruktor [] oder den Hashkonstruktor {} verwenden wollen. Hier ist die richtige Methode die vorangehenden beiden kaputten Codebruchstücke zu implementieren:

    for $i (1..10) {
        @array = somefunc($i);
        $AvA[$i] = [ @array ];
    }

Die eckigen Klammern erzeugen eine Referenz auf ein neues Array mit einer Kopie von dem was sich zum Zeitpunkt der Zuweisung in @array befindet. Das ist das, was du haben wolltest.

Beachte, dass dies etwas ähnliches bewerkstelligt aber viel schlechter zu lesen ist:

    for $i (1..10) {
        @array = 0 .. $i;
        @{$AvA[$i]} = @array;
    }

Ist das dasselbe? Nun, vielleicht ja -- vielleicht auch nicht. Der subtile Unterschied ist der, dass wenn man etwas in eckige Klammern stellt und zuweist, man sicher sein kann, dass es immer eine nagelneue Referenz auf eine frische Kopie der Daten ist. In dem neuen Fall mit der Dereferenzierung @{$AvA[$i]}} auf der linken Seite der Zuweisung könnte etwas anderes passieren. Das hängt ganz davon ab ob $AvA[$i] zu Beginn undefiniert war oder ob es bereits eine Referenz enthielt. Hattest Du @AvA bereits mit Referenzen bevölkert wie in

    $AvA[3] = \@anderes_array;

so würde die Zuweisung mit der Dereferenzierung auf der linken Seite die existierende Referenz, die schon vorhanden wäre verwenden:

    @{$AvA[3]} = @array;

Dies hätte natürlich den "interessanten" Nebeneffekt, den Inhalt von @anderes_array zu überschreiben. (Ist Dir jemals aufgefallen, dass wenn ein Programmierer sagt, etwas sei "interessant", er anstatt "faszinierend" mit verstörend größerer Wahrscheinlichkeit meint, dass es "nervig", "schwierig" oder beides ist? smile

Denke also einfach daran, immer die Array- oder Hashkonstruktoren mit [] oder {} zu verwenden, und du bist aus dem Schneider, obwohl das nicht immer optimal effizient ist.

Erstaunlicherweise wird das folgende gefährlich aussehende Konstrukt in der Tat wunderbar funktionieren:

    for $i (1..10) {
        my @array = somefunc($i);
        $AvA[$i] = \@array;
    }

Das liegt daran, dass my() mehr eine Laufzeitanweisung als eine Kopilationszeitdeklaration per se ist. Das heißt, dass die mit my() deklarierte Variable bei jedem Schleifendurchlauf neu angelegt wird. Obwohl es also so aussieht als ob man jedesmal dieselbe Variablenreferenz speichern würde, tut man es in Wirklichkeit nicht! Das ist ein subtiler Unterschied, der effizienteren Code erzeugen kann, auf die Gefahr hin, alle außer den erfahrensten Programmierern in die Irre zu führen. Ich spreche mich daher in der Regel dagegen aus, es Anfängern beizubringen. In der Tat sehe ich selten gerne die Verwendung des "Gib mir eine Referenz" Operators (Backslash) außer für die Übergabe von Parametern an Funktionen oft im Code. Stattdessen empfehle ich Anfängern, dass sie (und der größte Teil des Rests von uns) versuchen sollten, die viel einfacher verständlichen Konstruktoren [] und {} zu verwenden anstatt sich darauf zu verlassen, dass lexikalische (oder dynamische) Begrenzung des Geltungsbereiches und der versteckte Referenzzählungsmechanismus hinter den Kulissen das Richtige tun.

[Anmerkung des Übersetzers: Wenn man Leuten immer empfiehlt, die Möglichkeiten einer Sprache nicht voll auszunutzen, lernen sie wertvolle Kniffe nie. Da kann man auch gleich Logo zum Programmieren empfehlen ;-]

Im Überblick:

    $AvA[$i] = [ @array ];      # in der Regel am besten
    $AvA[$i] = \@array; # gefährlich; welchen Geltungsbereich hatte
                                # das Array noch mal?
    @{ $AvA[$i] } = @array;     # viel zu trickreich für die meisten
                                # Programmierer

WORTE DER WARNUNG ZUM RANG DER OPERATOREN

Da wir gerade über Dinge wie @{$AvA[$i]} reden, die folgenden Schreibweisen sind in der Tat das gleiche:

    $aref->[2][2]       # klar
    $$aref[2][2]        # verwirrend

Das liegt daren, dass Perls Rangfolgeregeln für seine fünf vorangestellten Dereferenzierungsindikatoren (die so aussehen, als ob jemand fluchen würde: $ @ * % & ) dafür sorgen, dass sie stärker binden als nachgestellte Subskripte in eckigen oder geschweiften Klammern! Ohne Zweifel ist dies wohl ein großer Schock für einen C oder C++ Programmierer, der es gewohnt ist, *a[i] zu benutzen um "das, worauf das i-te Element von a zeigt" auszudrücken. Es wird also erst das Subskript genommen und erst dann die Sache an dieser Stelle dereferenziert. Das ist prima in C, aber hier geht es nicht um C.

Die scheinbar äquivalente Konstruktion $$aref[$i] in Perl führt zuerst die Dereferenzierung von $aref aus, indem $aref als Arrayreferenz aufgefasst wird und dann dereferenziert wird, und gibt Dir schließlich den i-ten Wert des Arrays zurück, auf das $aref zeigt. Wolltest Du die C Interpretation haben, so müsstest Du ${$aref[$i]} schreiben um zu erzwingen, dass $aref[$i] zuerst ausgewertet wird, bevor der führende Dereferenzierungsindikator $ interpretiert wird.

WARUM MAN STETS use strict BENUTZEN SOLLTE

Wenn das hier anfängt erschreckender als es das wert ist zu klingen, entspanne Dich. Perl hat einige Fähigkeiten um Dir bei der Vermeidung der am meisten verbreiteten Fallen zu helfen. Die beste Methode um zu Verwirrung zu vermeiden, ist es, jedes Programm so zu beginnen:

    #!/usr/bin/perl -w
    use strict;

Auf diese Weise wirst Du gezwungen, alle Variablen mit my() zu deklarieren und versehentliche "symbolische Dereferenzierung" nicht mehr zu erlauben. Hättest Du also das hier geschrieben:

    my $AvA = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "elroy", "judy", ],
    ];

    print $AvA[2][2];

Würde der Compiler den letzten Befehl sofort zum Zeitpunkt der Kompilation als Fehler markieren, da Du versehentlich auf @AvA , eine nicht deklarierte Variable, zugegriffen hast und dadurch würde er Dich daran erinnern, stattdessen

    print $AvA->[2][2]

zu schreiben.

DEBUGGING

Vor der Version 5.002 machte der Standardperldebugger das Ausgeben von komplexen Datenstrukturen nicht besonders gut. Ab 5.002 aufwärts enthält der Debugger mehrere neue Fähigkeiten, darunter sowohl eine Kommandozeile mit Bearbeitungsfunktionen als auch das Kommando x um komplexe Datenstrukturen auszudrucken. Zum Beispiel ist hier die Debuggerausgabe für den oben zugewiesenen Inhalt von $AvA:

    DB<1> x $AvA
    $AvA = ARRAY(0x13b5a0)
       0  ARRAY(0x1f0a24)
          0  'fred'
          1  'barney'
          2  'pebbles'
          3  'bambam'
          4  'dino'
       1  ARRAY(0x13b558)
          0  'homer'
          1  'bart'
          2  'marge'
          3  'maggie'
       2  ARRAY(0x13b540)
          0  'george'
          1  'jane'
          2  'elroy'
          3  'judy'

CODEBEISPIELE

Mit wenigen Kommentaren (die bekommen irgendwann ihre eigenen Handbuchseiten) werden hier kurze Codebeispiele präsentiert, die den Zugriff auf verschiedene Typen von Datenstrukturen illustrieren.

ARRAYS VON ARRAYS

Deklaration eines ARRAYS VON ARRAYS

 @AvA = (
        [ "fred", "barney" ],
        [ "george", "jane", "elroy" ],
        [ "homer", "marge", "bart" ],
      );

Erzeugen eines ARRAYS VON ARRAYS

 # durch Einlesen aus einer Datei
 while ( <> ) {
     push @AvA, [ split ];
 }

 # durch Funktionsaufrufe
 for $i ( 1 .. 10 ) {
     $AvA[$i] = [ irgendEineFunktion($i) ];
 }

 # unter Verwendung temporärer Variablen
 for $i ( 1 .. 10 ) {
     @tmp = irgendEineFunktion($i);
     $AvA[$i] = [ @tmp ];
 }

 # Hinzufügen zu einer bestehenden Zeile
 push @{ $AvA[0] }, "wilma", "betty";

Zugriff auf und Ausgabe eines ARRAYS VON ARRAYS

 # einzelnes Element
 $AvA[0][0] = "Fred";

 # ein anderes Element
 $AvA[1][1] =~ s/(\w)/\u$1/;

 # Das ganze Ding unter Verwendung von Referenzen ausgeben
 for $aref ( @AvA ) {
     print "\t [ @$aref ],\n";
 }

 # Das ganze Ding mittels Indizes ausgeben
 for $i ( 0 .. $#AvA ) {
     print "\t [ @{$AvA[$i]} ],\n";
 }

 # Das ganze Ding elementweise ausgeben
 for $i ( 0 .. $#AvA ) {
     for $j ( 0 .. $#{ $AvA[$i] } ) {
         print "elt $i $j is $AvA[$i][$j]\n";
     }
 }

HASHES VON ARRAYS

Deklaration eines HASHES VON ARRAYS

 %HvA = (
        flintstones        => [ "fred", "barney" ],
        jetsons            => [ "george", "jane", "elroy" ],
        simpsons           => [ "homer", "marge", "bart" ],
      );

Erzeugen eines HASHES VON ARRAYS

 # durch Auslesen einer Datei der Form
 # flintstones: fred barney wilma dino
 while ( <> ) {
     next unless s/^(.*?):\s*//;
     $HvA{$1} = [ split ];
 }

 # durch Auslesen einer Datei; mit mehr temporären Variablen
 # flintstones: fred barney wilma dino
 while ( $line = <> ) {
     ($wer, $rest) = split /:\s*/, $line, 2;
     @felder = split ' ', $rest;
     $HvA{$wer} = [ @felder ];
 }

 # durch Aufruf einer Funktion, die Listen zurückgibt
 for $gruppe ( "simpsons", "jetsons", "flintstones" ) {
     $HvA{$gruppe} = [ lade_familie($gruppe) ];
 }

 # ähnlich, aber mit temporären Variablen
 for $gruppe ( "simpsons", "jetsons", "flintstones" ) {
     @mitglieder = hole_familie($gruppe);
     $HvA{$gruppe} = [ @mitglieder ];
 }

 # Hinzufügen neuer Familienmitglieder
 push @{ $HvA{"flintstones"} }, "wilma", "betty";

Zugriff auf und Ausgabe eines HASHES VON ARRAYS

 # einzelnes Element
 $HvA{flintstones}[0] = "Fred";

 # ein anderes Element
 $HvA{simpsons}[1] =~ s/(\w)/\u$1/;

 # Das ganze Ding ausgeben
 foreach $familie ( keys %HvA ) {
     print "$familie: @{ $HvA{$familie} }\n"
 }

 # Das ganze Ding mit Indizes ausgeben
 foreach $familie ( keys %HvA ) {
     print "Familie: ";
     foreach $i ( 0 .. $#{ $HvA{$familie} } ) {
         print " $i = $HvA{$familie}[$i]";
     }
     print "\n";
 }

 # Das ganze Ding sortiert nach Anzahl der Mitglieder ausgeben
 foreach $familie ( sort { @{$HvA{$b}} <=> @{$HvA{$a}} } keys %HvA ) {
     print "$familie: @{ $HvA{$familie} }\n"
 }

 # Das ganze Ding sortiert nach Anzahl der Mitglieder und Name
 # ausgeben
 foreach $familie ( sort {
                            @{$HvA{$b}} <=> @{$HvA{$a}}
                                        ||
                                    $a cmp $b
            } keys %HvA )
 {
     print "$familie: ", join(", ", sort @{ $HvA{$familie} }), "\n";
 }

ARRAYS VON HASHES

Deklaration eines ARRAYS VON HASHES

 @AvH = (
        {
            vater    => "fred",
            freund   => "barney",
        },
        {
            vater    => "george",
            mutter   => "jane",
            sohn     => "elroy",
        },
        {
            vater    => "homer",
            mutter   => "marge",
            sohn     => "bart",
        }
  );

Erzeugen eines ARRAYS VON HASHES

 # durch Auslesen einer Datei der Form
 # vater=fred freund=barney
 while ( <> ) {
     $rec = {};
     for $feld ( split ) {
         ($schluessel, $wert) = split /=/, $feld;
         $rec->{$schluessel} = $wert;
     }
     push @AvH, $rec;
 }

 # durch Auslesen einer Datei der Form
 # vater=fred freund=barney
 # keine temporären Variablen
 while ( <> ) {
     push @AvH, { split /[\s+=]/ };
 }

 # durch Aufrufe einer Funktion, die eine Liste von Schlüssel/Wert
 # Paaren zurückgibt, zum Beispiel
 # "vater","fred","tochter","pebbles"
 while ( %felder = holeNaechstenSatzPaare() ) {
     push @AvH, { %felder };
 }

 # ähnlich, aber ohne temporäre Variablen
 while (<>) {
     push @AvH, { parseSatzPaare($_) };
 }

 # Hinzufügen eines Schlüssel/Wert Paares zu einem Element
 $AvH[0]{haustier} = "dino";
 $AvH[2]{haustier} = "des Weihnachtsmanns kleiner Helfer";

Zugriff auf und Ausgabe eines ARRAYS VON HASHES

 # einzelnes Element
 $AvH[0]{vater} = "fred";

 # ein anderes Element
 $AvH[1]{vater} =~ s/(\w)/\u$1/;

 # Das ganze Ding unter Verwendung von Referenzen ausgeben
 for $href ( @AvH ) {
     print "{ ";
     for $role ( keys %$href ) {
         print "$role=$href->{$role} ";
     }
     print "}\n";
 }

 # Das ganze Ding mit Indizes ausgeben
 for $i ( 0 .. $#AvH ) {
     print "$i ist { ";
     for $rolle ( keys %{ $AvH[$i] } ) {
         print "$rolle=$AvH[$i]{$rolle} ";
     }
     print "}\n";
 }

 # Das ganze Ding elementweise ausgeben
 for $i ( 0 .. $#AvH ) {
     for $rolle ( keys %{ $AvH[$i] } ) {
         print "element $i $rolle ist $AvH[$i]{$rolle}\n";
     }
 }

HASHES VON HASHES

Deklaration eines HASHES VON HASHES

 %HvH = (
        flintstones => {
                vater        => "fred",
                freund       => "barney",
        },
        jetsons     => {
                vater        => "george",
                mutter       => "jane",
                "sein junge" => "elroy",
        },
        simpsons    => {
                vater        => "homer",
                mutter       => "marge",
                kind         => "bart",
        },
 );

Erzeugung eines HASHES VON HASHES

 # durch Einlesen einer Datei der Form
 # flintstones: vater=fred freund=barney mutter=wilma haustier=dino
 while ( <> ) {
     next unless s/^(.*?):\s*//;
     $wer = $1;
     for $feld ( split ) {
         ($schluessel, $wert) = split /=/, $feld;
         $HvH{$wer}{$schluessel} = $wert;
     }

 # durch Einlesen einer Datei; mehr temporäre Variablen
 while ( <> ) {
     next unless s/^(.*?):\s*//;
     $wer = $1;
     $rec = {};
     $HvH{$wer} = $rec;
     for $feld ( split ) {
         ($schluessel, $wert) = split /=/, $feld;
         $rec->{$schluessel} = $wert;
     }
 }

 # durch Aufrufe einer Funktion, die eine Liste von Schlüssel/Wert
 # Paaren zurückgibt
 for $gruppe ( "simpsons", "jetsons", "flintstones" ) {
     $HvH{$gruppe} = { lade_familie($gruppe) };
 }

 # ähnlich, aber mit temporären Variablen
 for $gruppe ( "simpsons", "jetsons", "flintstones" ) {
     %mitglieder = lade_familie($gruppe);
     $HvH{$gruppe} = { %mitglieder };
 }

 # Hinzufügen neuer Familienmitglieder
 %neue_leute = (
     mutter   => "wilma",
     haustier => "dino",
 );

 for $wer (keys %neue_leute) {
     $HvH{flintstones}{$wer} = $neue_leute{$wer};
 }

Zugriff auf und Ausgabe eines HASHES VON HASHES

 # einzelnes Element
 $HvH{flintstones}{mutter} = "wilma";

 # ein anderes Element
 $HvH{simpsons}{vater} =~ s/(\w)/\u$1/;

 # Ausgabe des ganzen Dings
 foreach $familie ( keys %HvH ) {
     print "$familie: { ";
     for $rolle ( keys %{ $HvH{$familie} } ) {
         print "$rolle=$HvH{$familie}{$rolle} ";
     }
     print "}\n";
 }

 # Das ganze Ding ein wenig sortiert ausgeben
 foreach $familie ( sort keys %HvH ) {
     print "$familie: { ";
     for $rolle ( sort keys %{ $HvH{$familie} } ) {
         print "$rolle=$HvH{$familie}{$rolle} ";
     }
     print "}\n";
 }

 # Das ganze Ding nach Anzahl der Mitglieder sortiert ausgeben
 foreach $familie ( sort { keys %{$HvH{$b}} <=> keys %{$HvH{$a}} } keys %HvH ) {
     print "$familie: { ";
     for $rolle ( sort keys %{ $HvH{$familie} } ) {
         print "$rolle=$HvH{$familie}{$rolle} ";
     }
     print "}\n";
 }

 # Für jede Rolle eine Sortierposition (Rang) festlegen
 $i = 0;
 for ( qw(vater mutter sohn tochter freund haustier) ) { $rang{$_} = ++$i }

 # Jetzt das ganze Ding sortier nach Anzahl der Mitglieder ausgeben
 foreach $familie ( sort { keys %{ $HvH{$b} } <=> keys %{ $HvH{$a} } } keys %HvH ) {
     print "$familie: { ";
     # und die Mitglieder nach Sortierposition ausgeben
     for $rolle ( sort { $rang{$a} <=> $rang{$b} }  keys %{ $HvH{$familie} } ) {
         print "$rolle=$HvH{$familie}{$rolle} ";
     }
     print "}\n";
 }

KOMPLEXERE STRUKTUREN

Deckaration KOMPLEXER STRUKTUREN

Hier ist ein Beispiel, das zeigt, wie man eine Datenstruktur erzeugen und benutzen kann, deren Felder viele verschiedene Typen haben:

     $rec = {
         TEXT       => $zeichenkette,
         SEQUENZ    => [ @alte_werte ],
         LEXIKON    => { %irgend_eine_tabelle },
         JENERCODE  => \&irgend_eine_function,
         DIESERCODE => sub { $_[0] ** $_[1] },
         HANDLE    => \*STDOUT,
     };

     print $rec->{TEXT};

     print $rec->{SEQUENZ}[0];
     $last = pop @ { $rec->{SEQUENZ} };

     print $rec->{LEXIKON}{"schluessel"};
     ($erster_schluessel, $erster_wert) = each %{ $rec->{LEXIKON} };

     $answer = $rec->{JENERCODE}->($arg);
     $answer = $rec->{DIESERCODE}->($arg1, $arg2);

     # beachte zusätzliche geschweifte Blockklammern um die
     # Dateihandlereferenz 
     print { $rec->{HANDLE} } "irgendwelcher Text\n";

     use FileHandle;
     $rec->{HANDLE}->autoflush(1);
     $rec->{HANDLE}->print("irgendwelcher Text\n");

Deklaration eines HASHES VON KOMPLEXEN STRUKTUREN

     %TV = (
        flintstones => {
            serie      => "flintstones",
            tage       => [ qw(montag donnerstag freitag) ],
            mitglieder => [
                { name => "fred",    rolle => "vater",  alter => 36, },
                { name => "wilma",   rolle => "mutter", alter => 31, },
                { name => "pebbles", rolle => "kind",   alter =>  4, },
            ],
        },

        jetsons     => {
            serie      => "jetsons",
            tage       => [ qw(mittwoch samstag) ],
            mitglieder => [
                { name => "george",  rolle => "vater",  alter => 41, },
                { name => "jane",    rolle => "mutter", alter => 39, },
                { name => "elroy",   rolle => "kind",   alter =>  9, },
            ],
         },

        simpsons    => {
            serie      => "simpsons",
            tage       => [ qw(montag) ],
            mitglieder => [
                { name => "homer", rolle => "vater",  alter => 34, },
                { name => "marge", rolle => "mutter", alter => 37, },
                { name => "bart",  rolle => "kind",   alter => 11, },
            ],
         },
      );

Erzeugen eines HASHES VON KOMPLEXEN STRUKTUREN

     # durch Einlesen einer Datei
     # Am einfachsten geht das wenn man die Datei selbst in dem
     # Rohdatenformat wie oben gezeigt vorliegen hat. Perl
     # interpretiert gerne komplexe Datenstrukturen wenn sie als Daten
     # deklariert werden, also ist es manchmal am einfachsten, das zu
     # tun. 

     # Hier ein Stück für Stück Aufbau
     $rec = {};
     $rec->{serie} = "flintstones";
     $rec->{tage} = [ finde_tage() ];

     @mitglieder = ();
     # nehme an, dass diese Datei in Feld=Wert Syntax vorliegt
     while (<>) {
         %felder = split /[\s=]+/;
         push @mitglieder, { %felder };
     }
     $rec->{mitglieder} = [ @mitglieder ];

     # nun merke Dir das ganze Ding
     $TV{ $rec->{serie} } = $rec;

     #################################################################
     # Jetzt möchtest Du unter Umständen interessante zusätzliche
     # Felder anlegen, die Zeiger zurück auf die gleichen
     # Datenstrukturen enthalten, so dass sich eine Information, die
     # Du an einer Stelle änderst, überall ändert. Zum Beispiel
     # könntest Du ein {kinder} Feld haben wollen, das eine Referenz
     # auf ein Array mit den Strukturen der Kinder enthält, aber ohne
     # doppelte Strukturen und damit Probleme beim Ändern der Daten zu
     # haben.
     #################################################################
     foreach $familie (keys %TV) {
         $rec = $TV{$familie}; # temporärer Zeiger
         @kinder = ();
         for $person ( @{ $rec->{mitglieder} } ) {
             if ($person->{rolle} =~ /kind|sohn|tochter/) {
                 push @kinder, $person;
             }
         }
         # BEDENKE: $rec und $TV{$family} zeigen auf dieselben Daten!
         $rec->{kinder} = [ @kids ];
     }

     # Du hast eine Kopie des Arrays angelegt, aber der Array selbst
     # enthält Zeiger auf nicht kopierte Objekte. Das bedeutet, dass
     # wenn du Bart mittels

     $TV{simpsons}{kinder}[0]{alter}++;

     # altern lässt, schlägt sich die Veränderung auch in

     print $TV{simpsons}{mitglieder}[2]{alter};

     # nieder, denn $TV{simpsons}{kinder}[0] und
     # $TV{simpsons}{mitglieder}[2] zeigen beide auf dieselbe
     # darunterliegende anonyme Hashtabelle.

     # Das ganze Ding ausgeben
     foreach $familie ( keys %TV ) {
         print "die $familie";
         print " gibt es am @{ $TV{$familie}{tage} } zu sehen\n";
         print "ihre mitglieder sind:\n";
         for $wer ( @{ $TV{$familie}{mitglieder} } ) {
             print " $wer->{name} ($wer->{rolle}), alter $wer->{alter}\n";
         }
         print "es stellt sich heraus, dass $TV{$familie}{vater} ";
         print scalar ( @{ $TV{$familie}{kinder} } ), " kinder namens ";
         print join (", ", map { $_->{name} } @{ $TV{$familie}{kinder} } );
         print " hat\n";
     }

Datenbankanbindung

Du kannst eine vielschichtige Datenstruktur (wie einen Hash von Hashes) nicht so einfach mit einer dbm Datei verknüpfen. Das erste Problem ist, dass alle Datenbanken außer GDBM und Berkeley DB Gößenbeschränkungen haben, aber abgesehen davon stellt sich auch das Problem, wie Referenzen auf der Platte ausgedrückt werden sollten. Ein experimentelles Modul, dass teilweise versucht, dieses Problem anzupacken, ist das Modul MLDBM?. Suche auf deiner nähesten CPAN Seite, wie in perlmodlib beschrieben, nach dem Quellcode für MLDBM.

SIEHE AUCH

perlref, perllol, perldata, perlobj

AUTOR

Tom Christiansen < tchrist@perl.com >

ÜBERSETZER

Thomas Chust < chust@web.de >

Last update: Wed Oct 23 04:57:50 MET DST 1996


Kommentare:

-- HaraldBongartz - 29 Jun 2003
Topic revision: r3 - 2006-01-16 - 01:57:00 - ThomasChust
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.