PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Per Index auf einen assoziativen Array zugreifen

with 13 comments

Hä? Was ist das denn für eine Überschrift? Das geht doch garnicht! Richtig, normalerweise geht das nicht, aber mit etwas tricksen schon.

Wir haben das folgende Array und möchten den 4. Wert haben, ohne aber den Key zu kennen. Bei einem 0-basierten Array wäre es also das Element mit dem Index 3:

<?php

$array = array(
    '10.07.2011' => 14,
    '11.07.2011' => 51,
    '12.07.2011' => 46,
    '13.07.2011' => 9,
    '14.07.2011' => 20,
    '15.07.2011' => 37,
);

echo $array[3];

Wie zu erwarten erhalten wir eine Notice und keinen Wert:

Notice: Undefined offset: 3 in test.php on line 12

Doch wie kommen wir nun an das 4. Array-Element? Ich habe 3 Möglichkeiten für euch, zuerst die unschönen:

$i=0;
foreach ($array as $a) {
    if ($i == 3) {
        echo $a;
        break;
    }
    $i++;
}

Bei einem großen Array ist diese Methode natürlich sehr langsam. Vergleichbar schlecht der folgende Code:

for ($i=0; $i<3; $i++) {
    next($array);
}
echo current($array);

Besser ist die folgende Lösung:

echo current(array_slice($array, 3, 1));

Wer kennt noch eine?

Edit:

Zwei weitere Lösungen aus den Kommentaren, wahrscheinlich irgendwo in der Mitte anzusiedeln was die Geschwindigkeit bei sehr großen Arrays angeht:

$values = array_values($array);
echo $values[3];

Mit PHP 5.4 geht es sogar noch kürzer ohne das „Zwischenarray“:

echo array_values($array)[3];

Oder aber auch:

$keys = array_keys($array);
echo $array[$keys[3]];

PHP 5.4:

echo $array[array_keys($array)[3]];

Written by Michael Kliewe

Juli 13th, 2011 at 8:42 am

Posted in PHP

Tagged with , ,

13 Responses to 'Per Index auf einen assoziativen Array zugreifen'

Subscribe to comments with RSS or TrackBack to 'Per Index auf einen assoziativen Array zugreifen'.

  1. Moin Michael,

    für einzelne Werte macht array_slice() den meisten Sinn. Willst du mehrere Werte haben, kannst du das assoziative Array auch in ein numerisches überführen: php.net/array_values – dann kannst du wieder mit $array[123] darauf zugreifen.

    Rodney Rehm

    13 Jul 11 at 09:00

  2. Was ist einfach mit dem hier?

    $keys = array_keys($array);
    echo $array[$keys[3]];

    KingCrunch

    13 Jul 11 at 09:11

  3. Habe eure beiden Lösungen oben eingetragen.

    Michael Kliewe

    13 Jul 11 at 09:17

  4. Das ist ungeheuerlich, nur Gangsta würden so etwas tun! Oh, Moment… 😉

    Ich weiß zwar keinen Anwendungsfall, aber nette Idee.

    Timo Reitz

    13 Jul 11 at 09:35

  5. Schöne Lösungen, aber wo ist der Anwendungsfall? Habe mal darüber nachgedacht und keinen gefunden 🙂

    Nils

    13 Jul 11 at 10:14

  6. Noch eine Lösung für OO- und Java-Fanboys 😉

    $array = new ArrayIterator($array);
    $array->seek(3);
    echo $array->current();

    Ich hatte mal einen Anwendungsfall für dieses Problem. Hatte da die array_values() Lösung benutzt.

    Florian Heinze

    13 Jul 11 at 13:33

  7. Ich ab es mal fix verglichen:

    8. Wert von 10

    foreach/break: 4.0531158447266E-6
    for/next: 4.0531158447266E-6
    array_slice: 2.8610229492188E-6

    89. Wert von 100

    foreach/break: 1.1920928955078E-5
    for/next: 2.3841857910156E-5
    array_slice: 5.0067901611328E-6

    8999. Wert von 100

    foreach/break: 0.00072598457336426
    for/next: 0.0022101402282715
    array_slice: 4.0531158447266E-6

    Ziemlich eindeutig für array_slice.

    Oliver

    13 Jul 11 at 13:50

  8. @Florian Heinze:
    Das ist gemein, von einem Anwendungsfall schreiben und den dann nicht erwähnen! 😛

    Ich selbst trenne scharf zwischen indizierten Arrays und assoziativen Arrays – dass die in PHP durch die gleiche Datenstruktur realisiert werden, halte ich bis heute für kurios (und eine Monstrosität!).

    Timo Reitz

    13 Jul 11 at 13:54

  9. Nur bevor sich mal wer Umstände macht – den ersten und den letzten Wert bekommt man auch über reset()/end() geliefert.

    nk

    13 Jul 11 at 13:55

  10. @Oliver: Bei benchmarks mit derart kleinen Zahlen bin ich immer etwas skeptisch :X Hast auch die beiden zusätzlichen Varianten mit array_keys() und array_values() vergessen 😉

    @Timo Reitz:
    Indizierte und assoziative Arrays sind nur die Darstellungen. Genau genommen decken Arrays einiges mehr ab: arrays, maps, lists, stacks, queues, tupel, structs und mit Wohlwollen trees (und sicher hab ich noch einiges vergessen). Wenn man es noch strenger sieht, dann entspricht der Anwendungsfall genau den structs, denn die sind formal immer geordnet, also auch immer per Index ansprechbar 😉 Obs Sinn macht, oder nicht, sei mal dahing gestellt.

    Ich muss bei diesem Thema die ganze Zeit an die `fetch_array()`-Methoden diverser Datenbanktreiber denken. Die liefern wahlweise auch eine Repräsentation, die sowohl numerische (Spaltennummer), als auch assoziative (Spaltenname) Schlüssel bereit stellt. Da wirkt es auch mich schon schlüssiger, wenn man auf rein-assoziative auch per Index zugreifen könnte.

    Als Randbemerkung: Finds gut, dass hier auch gleich noch Werbung für PHP5.4 gemacht wird 🙂 PHP hats echt mal nötig sein Ruf als Dauer-Veraltet los zu werden

    KingCrunch

    13 Jul 11 at 14:42

  11. array_slice ist die effektive Lösung. Alle anderen Lösungen können da nicht ansatzweise mithalten.

    Die Funktion macht im Grunde dasselbe wie eine entsprechende for-Schleife in PHP. Nur macht array_slice das in C-Code, also im wahrsten Sinne des Wortes ein paar Takte schneller.

    http://svn.php.net/viewvc/php/php-src/tags/php_5_3_6/ext/standard/array.c?view=markup#l2143

    Das bestätigt wieder die Regel, auf Core-Funktionen zu setzen, wo es nur geht.

    Benchmark: http://pastie.org/2210713

    Selbst die Lösung mit array_keys ist (auf meinem Rechner) unsignifikant schneller als diverse Schleifenansätze, verbraucht aber durch das Kopieren der Schlüssel wesentlich mehr Speicher.

    Die next/current-Lösung ist die langsamste von allen. Das schiebe ich darauf, dass viele Funktionsaufrufe getätigt werden müssen.

    Noch eine interessante Beobachtung zu Referenzen und Copy-On-Write-Verhalten:

    Wird $array in der Funktionssignatur als Referenz ausgewiesen, verringert sich der Speicherverbauch bei den Varianten next/current und foreach, während er bei array_slice und array_keys anwächst.

    Das liegt vermutlich daran, dass sowohl array_slice als auch array_keys einen Array-Zeiger-Reset durchführen (zend_hash_internal_pointer_reset_ex), was bei Arrays, die als Referenz vorliegen, nicht geduldet wird und deshalb Copy-On-Write auslöst.

    Bei der foreach-Variante löst zudem…

    foreach ($array as $key => $value) {

    …kein Copy-On-Write von $array aus…

    foreach ($array as $key => &$value) {

    …dagegen schon.

    mermshaus

    14 Jul 11 at 06:48

  12. PS: Das Kopieren bremst array_slice und array_keys dann wohl verständlicherweise ganz schön aus.

    mermshaus

    14 Jul 11 at 06:52

  13. Es gibt noch weitere Lösungen,

    $array = array(
    ‚10.07.2011‘ => 14,
    ‚11.07.2011‘ => 51,
    ‚12.07.2011‘ => 46,
    ‚13.07.2011‘ => 9,
    ‚14.07.2011‘ => 20,
    ‚15.07.2011‘ => 37,
    );

    $num = count($array);
    $ar = array_fill(0, $num, 1);
    $ar = array_keys($ar);
    $ar = array_combine($ar, $array);

    Wäre zum Beispiel noch eine, die im Prinzip aber genau dasselbe macht wie array_values, nur umständlicher.

    Mit wäre jetzt auch kein Anwendungsfall bekannt, bei dem man wirklich per numerischem Index auf einen assoziativen Array zugreifen muss. Eigentlich ist das ja auch nicht der Sinn eines solchen Arrays, der eigentlich eine HashMap ist.

    Patrick

    14 Jul 11 at 15:27

Leave a Reply

You can add images to your comment by clicking here.