You are here: Perldoc Web>PerlDokumentListe>Perllol (2008-08-23)

perllol

perllol Dokumentation | Download als POD | Wie kann ich hier etwas ändern?

NAME

perllol - Arrays von Arrays mit Perl manipulieren

BESCHREIBUNG

Deklaration und Zugriff auf Arrays von Arrays

Einen Array von Arrays (manchmal auch etwas ungenauer eine Liste von Listen genannt) zu bauen, ist das einfachste von der Welt. Es ist ziemlich einfach zu verstehen und fast alles, was hier erklärt wird, wird auch später in den komplexeren Datenstrukturen verwendbar sein.

Ein Array von Arrays ist einfach ein normaler Array @AoA auf den man mit zwei Indexen zugreifen kann, in etwa so $AoA[3][2] . Hier ist eine Deklaration eines solchen Arrays:

    # wir weisen unserem Array einen Array mit Arrayreferenzen zu
    @AoA = (
        [ "fred", "barney" ],
        [ "george", "jane", "elroy" ],
        [ "homer", "marge", "bart" ],
    );

    print $AoA[2][2];
  bart

Man sollte stark darauf achten, dass die äusseren Klammern runde sind. Das ist wichtig, da man etwas einem Array zuweist, daher braucht man runde Klammern. Wenn du keinen @AoA sondern nur eine Referenz darauf willst, könntest du so etwas machen:

    # Zuweisung einer Referenz auf einen Array von Arrayreferenzen
    $ref_to_AoA = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "elroy", "judy", ],
    ];

    print $ref_to_AoA->[2][2];

Beachte, dass sich der Typ der umschliessenden Klammern genau so wie die Syntax für den Zugriff geändert hat. Das ist so, da anders als in C in Perl Arrays und Referenzen darauf nicht beliebig ausgetauscht werden können. $ref_to_AoA ist eine Referenz auf ein Array genau so wie @AoA ein echtes Array ist. Gleichsam ist $AoA[2] kein Array, sondern eine Refernez darauf. Also warum kannst du folgendes schreiben:

    $AoA[2][2]
    $ref_to_AoA->[2][2]

anstatt folgendes schreiben zu müssen:

    $AoA[2]->[2]
    $ref_to_AoA->[2]->[2]

Nun, das kann man, da die Definition besagt, dass man lediglich bei aufeinanderfolgenden Klammern (entweder eckige oder geschweifte) den dereferenzierenden Pfeil weglassen darf. Aber dies darf man nicht für den aller ersten, falls es ein Scalar ist, der eine Referenz beinhaltet. Das heisst, dass $ref_to_AoA den Pfeil immer benötigt.

Bau dir deine Eigenen

Das ist alles gut und schön für die Deklaration von statischen Datenstrukturen, aber was ist, wenn du spontan neue Elemente hinzufügen willst, oder alles von Grund auf bauen möchtest?

Lass uns erst mal schauen, wie man so etwas aus einer Datein ausliesst. Das ist in etwa so, als würde man eine Reihe nach der anderen hinzufügen. Wir gehen mal davon aus, das jede Zeile eine Reihe und jedes Wort ein Element darstellt. Falls du nun einen @AoA bauen willst, der all das enthält, dann ist hier der richtige Weg um das zu tun:

    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

Du kannst dir das aber auch aus einer Funktion holen:

    for $i ( 1 .. 10 ) {
        $AoA[$i] = [ somefunc($i) ];
    }

Oder du benutzt eine temporäre Variable, um die Werte zwischen zu speichern.

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

Es ist sehr wichtig, dass du dich vergewisserst, den [] Array Referenz Konstruktor zu benutzen, denn das hier ist sehr falsch:

    $AoA[$i] = @tmp;

Siehst du, einen Array wie diesen einem Scalar zuzuweisen, zählt einfach die Elemente in @tmp , was warscheinlich nicht das ist, was du willst.

Falls du mit use strict arbeitest, wirst du einige Deklarationen hinzufügen müssen, um es glücklich zu machen:

    use strict;
    my(@AoA, @tmp);
    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

Natürlich musst du dem temporären Array gar keinen Namen geben:

    while (<>) {
        push @AoA, [ split ];
    }

Du musst auch nicht unbedingt push() benutzen. Du könntest einfach eine direkte Zuweisung machen, falls du weisst, wo du es hin haben möchtest:

    my (@AoA, $i, $line);
    for $i ( 0 .. 10 ) {
        $line = <>;
        $AoA[$i] = [ split ' ', $line ];
    }

oder auch nur

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', <> ];
    }

Du solltest grundsätzlich misstrauisch gegenüber Funktionen sein, die Listen in Scalarem Kontext zurückgeben könnten, ohne das deutlich zu erwähnen. Dies währe für den durchschnittlichen Leser besser zu verstehen:

    my (@AoA, $i);
        for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', scalar(<>) ];
    }

Falls du lieber eine $ref_to_AoA Varaible als Referenz auf einen Array haben möchtest, dann müsstest du so etwas machen:

    while (<>) {
        push @$ref_to_AoA, [ split ];
    }

Jetzt kannst du neue Reien hinzufügen. Aber was ist, wenn du neue Werte hinzufügen möchtest? Falls du es eifach nur mit Matrizen zu tun hast, ist es das einfachste, wenn du eine einfache Zuweisung benutzt:

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            $AoA[$x][$y] = func($x, $y);
        }
    }

    for $x ( 3, 7, 9 ) {
        $AoA[$x][20] += func2($x);
    }

Es ist egal, ob diese Elemente schon da sind, oder nicht, Perl wird sie gerne für dich erstellen und alle Elemente dazwischen auf undef setzen, so das nötig sein sollte.

Falls du lediglich etwas an eine Reihe anhängen möchtest, musst du etwas tun, was ein wenig komischer aussieht:

    # neue Elemente an eine Reihe anhängen
    push @{ $AoA[0] }, "wilma", "betty";

Beachte, dass ich nicht einfach folgendes tun konnte:

    push $AoA[0], "wilma", "betty";  # FALSCH!

Tatsächlich würde das noch nicht einmal compiliert werde. Wie das? Weil das erste Argumen von push() ein eches Array und nicht nur eine Referenz darauf sein muss.

Zugriff und Ausgabe

Jetzt ist es and der Zeit, deine Datenstruktur auszugeben. Wie wirst du das tun? Nun, wenn du nur eines der Elemente haben willst, ist das einfach:

    print $AoA[0][0];

Wenn du aber das ganze Konstrukt ausgeben willst, kannst du nicht einfach folgendes sagen:

    print @AoA;     # FALSCH

denn du wirst nur Referenzen aufgelistet bekommen, und perl wird niemals automatisch automatisch Dinge für dich dereferenzieren. Statt dessen musst du dir ein oder zwei Schleifen bauen. Das hier gibt die gesamte Struktur aus, indem es eine for-Schleife benuzt, die den ersten Index des Arrays durchgeht.

    for $aref ( @AoA ) {
        print "\t [ @$aref ],\n";
    }

Falls du wissen möchtest, auf welchem Index du dich gerade befindest, möchtest du vielleicht so etwas tun:

    for $i ( 0 .. $#AoA ) {
        print "\t elt $i is [ @{$AoA[$i]} ],\n";
    }

oder vielleicht sogar so etwas. Beachte die innere Schleife.

    for $i ( 0 .. $#AoA ) {
        for $j ( 0 .. $#{$AoA[$i]} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

Wie du sehen kannst, wird's ein bischen kompliziert. Daher ist es manchmal einfacher, sich eine temporäre Referenz mitzunehmen:

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        for $j ( 0 .. $#{$aref} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

Hmm... das ist immer noch ein wenig unschön. Wie währ's hiermit:

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        $n = @$aref - 1;
        for $j ( 0 .. $n ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

Slices

Wenn du einen Slice (ein Teil einer Zeile) in einem multidimensionalen Array haben willst, musst du etwas ausgefallener Indexieren. Das kommt daher, dass wir wohl für einzelne Elemente mittels des Pfeil-Operators einfach dereferenzieren können, uns aber für Slices eine solche Möglichkeit fehlt. (Natürlich können wir jederzeit Slices mittels einer for-Schleife auslesen.)

Hier ist ein Weg, wie man so einen Slice mittels einer Schleife bekommt. Wir nehmen einmal an, dass wie schon vorher @AoA verfügbar ist.

    @part = ();
    $x = 4;
    for ($y = 7; $y < 13; $y++) {
        push @part, $AoA[$x][$y];
    }

Diese Schleife könnte man auch mit einer Slice Operation ersetzen:

    @part = @{ $AoA[4] } [ 7..12 ];

Aber wie dir sicher schon aufgefallen ist, währe das nur schwer lesbar.

Ah, aber was ist, wenn du einen zweidimensionalen Slice haben möchtest, der auf $x von 4 bis 8 und auf $y von 7 bis 12 geht? Hmm... hier ist eine einfache Lösung:

    @newAoA = ();
    for ($startx = $x = 4; $x <= 8; $x++) {
        for ($starty = $y = 7; $y <= 12; $y++) {
            $newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
        }
    }

Wir können eine for Schleife durch benutzung eines Slices einsparen.

    for ($x = 4; $x <= 8; $x++) {
        push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
    }

Falls du dich für Schwartzsche Transformation interessierst, hättest du warscheinlich map dafür benutzt.

    @newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;

Obwohl, wenn dein Vorgesetzter dich beschuldigen würde, dass du dir deinen Job durch unübersichtlichen Code sichern willst, währe das schwer zu wiederlegen. smile Wenn ich du währe, würde ich das in eine Funktion auslagern:

    @newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
    sub splice_2D {
        my $lrr = shift;    # Referenz auf ein Array mit Arrayrefernezen!
        my ($x_lo, $x_hi,
            $y_lo, $y_hi) = @_;
    
        return map {
            [ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
        } $x_lo .. $x_hi;
    }

SIEHE AUCH

perldata(1), perlref(1), perldsc(1)

AUTOR

Tom Christiansen < tchrist@perl.com >

Letzte Aktualisierung (des Originals): Thu Jun 4 16:16:23 MDT 1998

Übersetzer

Jürgen "Taulmarill" Peters taulmarill [at] xgn [dot] de


Kommentare:
Topic attachments
I Attachment Action Size Date Who Comment
perllol.podpod perllol.pod manage 9.4 K 2005-08-03 - 14:09 JuergenPeters vollständig, aber noch nicht kontrolliert
Topic revision: 2008-08-23, LanX
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.