Ändern eines Arrays während einer foreach-Schleife

123119
Capi Etheriel

Diese Frage beschäftigt mich immer. Normalerweise ändere ich das Array, während ich durch seine Tasten gehe, abhängig von meinen Bauchgefühlen:

foreach (array_keys($array) as $key) {
  $array[$key] = perform_changes_on($array[$key]);
}

Ich kenne jedoch andere Methoden. Das Arrayelement kann als Referenz beim Aufruf von foreach übergeben werden:

foreach ($array as &$item) {
  $item = perform_changes_on($item);  
}

Schließlich kann das Array direkt während der Schleife geändert werden:

foreach ($array as $key => $value) {
    $array[$key] = perform_changes_on($value);
}

Was sind die Auswirkungen auf die Leistung und die Sicherheit jedes Ansatzes? Gibt es einen empfohlenen Ansatz?

UPDATE: Was mir eigentlich Sorgen bereitet, ist, dass die beiden letzten Ansätze das Array modifizieren, während ich es in einer Schleife durchführe.

Antworten
28
http://stackoverflow.com/questions/10057671/how-foreach-actually-works ist hilfreich. chx vor 6 Jahren 0
OP hat tatsächlich Code gegeben und ist doch geschlossen, weil der Code nicht Teil einer "konkreten Implementierung" ist. ehrlich, was? Blauhirn vor 3 Jahren 1

5 Antworten auf die Frage

15
chx

Wenn Sie sich Sorgen machen, das Array während der Schleife zu ändern, sollten Sie es nicht tun, da foreach das Array kopiert. Versuchen

$array = array('foo');
foreach ($array as $k => &$item) {
  $array[] = 'bar';
}
var_dump($array);

Und sehen Sie, es endet gut. foreach ($array as $k => &$v)ist eine Abkürzung für foreach (array_keys($array) as $k) $v = &$array[$k]so, solange es noch eine Kopie des Arrays gibt (deshalb habe ich es &$itemin meinem Beispiel verwendet, damit Sie sehen können, wenn Sie das Array ändern, wird es in der Referenz geändert!).

$array = array('foo', 'bar');
foreach ($array as $k => $item) {
  echo "$item\n";
  if (!$k) {
    $array[1] = 'baz';
  }
}
$array = array('foo', 'bar');
foreach ($array as $k => &$item) {
  echo "$item\n";
  if (!$k) {
    $array[1] = 'baz';
  }
}

die erste Deponie foo und bar, die zweite foo and baz.

"foreach ($ array als $ k => $ v) ist eine Abkürzung für foreach (array_keys ($ array) als $ k) $ v = & $ array [$ k]" Dies ist keine sehr kurze Auswahl ein Unterschied, wenn man eine Funktion und ein Sprachkonstrukt aufruft (obwohl dies aus praktischen Gründen eine Abkürzung ist). Es wäre auch eine Abkürzung für eine Kopie, keine Referenz. (PHP ist auch eine Kopie beim Schreiben, daher können Sie normalerweise zusätzliche Kopien ignorieren.) Corbin vor 8 Jahren 0
Offensichtlich wollte das `foreach ($ array as $ k => & $ v)` bearbeitet und behoben werden. chx vor 8 Jahren 0
Ich möchte darauf hinweisen, dass `if (! $ K)` für 0 TRUE ist, andernfalls ist diese Anweisung immer falsch. Im Wesentlichen überschreiben Sie also das zweite Array-Element, bevor Sie es jemals sehen. mseancole vor 8 Jahren 0
Ich mache das und damit zeige ich, dass der erste foreach von einer Kopie arbeitet und der zweite nicht. chx vor 8 Jahren 0
@chx Vielleicht möchten Sie dann Ihr erstes Snippet ausführen. $ item ist eine Kopie eines Wertes aus dem $ -Array. `$ array` wird niemals kopiert (obwohl alle Elemente von` $ array` kopiert werden, wenn die foreach-Schleife abgeschlossen ist). Der erste Ausschnitt wird wie der zweite Schnipsel abfangen. Corbin vor 8 Jahren 0
(Beachten Sie, dass Sie in Ihrer Antwort dump für die Ausgabe während der Schleife verwendet haben, während ich damit die Ausgabe des gesamten Arrays nach der Schleife bezeichnet habe. Grundsätzlich wie in: http://paste.ee/p/hzqGz - - kurz gesagt, ich antworte auf Ihren Kommentar unter der Annahme, dass er den Zeilen Ihrer Antwort folgt: "foreach kopiert das Array.") Corbin vor 8 Jahren 0
Lustig. Ich lief diesen Ausschnitt. Ich bekomme Foo, Bar, Foo, Baz. chx vor 8 Jahren 0
@chx, ist dies nicht nur ein Implementierungsdetail, das in der Zukunft geändert werden könnte? Die Dokumentation (Spezifikation) besagt nicht, dass "foreach" als solche funktionieren muss. Pacerier vor 5 Jahren 0
@Corbin: Ich denke, @chx bedeutet, dass alle `$ item`s für jedes Schlüssel-Wert-Paar bei der Instantiierung kopiert werden, sodass` $ array [1] `in der ersten Schleife in` baz` geändert wird Die zweite Schleife druckt noch immer "bar" als Wert von "$ item", obwohl sie im eigentlichen Array bereits aktualisiert wurde. klaar vor 4 Jahren 0
10
Konrad Borowski

Das klingt nach einem Ort für array_map().

$array = array_map('perform_changes_on', $array);
schön, aber manchmal werde ich nicht jedes Element in dem Array ändern. Ich habe gerade versucht, das Beispiel einfacher zu machen ... Capi Etheriel vor 8 Jahren 0
`perfom_changes_on` muss nicht jedes Element ändern. Beachten Sie, dass es andere [Array-Funktionen] (http://www.php.net/manual/de/ref.array.php) gibt, einige leistungsfähiger als andere. [array_reduce] (http://www.php.net/manual/de/function.array-reduce.php) kann beispielsweise sehr helfen. Quentin Pradet vor 8 Jahren 1
Ist dies nicht ein Funktionsaufruf für jedes Element? Kann nicht die beste Leistung sein. Tom vor 5 Jahren 0
@Tom: Ich würde nicht sagen, dass der Aufwand für den Funktionsaufruf bemerkenswert ist. Wartungsfreundlichkeit ist viel wichtiger als sehr kleine Leistungssteigerungen (ich bin mir nicht einmal sicher, ob sie überhaupt existieren). Wenn Sie jedoch der Meinung sind, dass das Schreiben von 100.000 Zeilen ohne Funktionen die beste Art der Programmierung ist, tun Sie dies auf jeden Fall. Seien Sie nicht überrascht, wenn niemand den Code beibehalten möchte, und Sie werden nicht verstehen, was Sie einen Tag später geschrieben haben. Konrad Borowski vor 5 Jahren 0
3
chx

foreach($array as &$item)Vergessen Sie bei der Verwendung niemals unset($item);das Foreach oder Sie werden in ernsthafte Schwierigkeiten geraten, wenn Sie es $itemspäter versuchen . Es sollte üblich sein, diese Falle zu vermeiden.

Im Allgemeinen sollten Sie vermeiden, foreach ...&und zu tun, array_walk($array, function (&$item) {...so dass die Referenz streng innerhalb des Verschlusses beschränkt ist.

Danke für die Antwort, ich verwende array_keys, um den Trap zu vermeiden, aber muss ich ihn verwenden? Ich meine, foreach ($ array as $ key => $ item) `sollte mich den Wert von $ array [$ key]` ändern lassen, aber wer weiß, wie PHP intern arbeitet ... das ist eigentlich die Frage. Capi Etheriel vor 8 Jahren 0
1
mseancole

Ich kann dir nicht wirklich mit dem Performance-Bit helfen, ich sage dir nur, dass du sie in microtime()Tags einwickelst und sehen solltest, welche für dich besser ist. Systeme sind etwas anders. Der eine Vorschlag, den ich Ihnen geben kann, ist, array_keys()aus Ihrem Code zu entfernen .

!!AKTUALISIEREN!!

Wenn Sie Corbin und meinem Argument unten gefolgt sind, dann habe ich endlich eine Antwort für Sie. Ich war nach und nach verwirrt. Für und While - Schleifen Sie alle Funktionen auf jeder Iteration übergeben als Argumente nennen, foreach nicht . Deshalb ist es besser, Funktionen wie strlen()und count(), um nur einige Beispiele zu nennen, außerhalb einer for oder while-Schleife aufzurufen . Der Aufwand, den wir erlebten, war nicht von foreach, sondern von array_keys(). Beim array_keys()Aufruf muss ein neues Array generiert werden, weshalb es fast doppelt so langsam ist. Daher ist es am besten, die array_keys()Funktion zusammen zu löschen und einfach über das Array zu iterieren und das Schlüsselwertpaar abzurufen. Wir entschuldigen uns für jegliche Verwirrung, die dies verursacht haben könnte.

Quellen:

!! ENDE DER AKTUALISIERUNG !!

Meines Wissens gibt es bei keiner dieser Implementierungen ein Sicherheitsrisiko. Sie iterieren ein Konstrukt, das bereits existiert. Sicherheitsprobleme wären vor diesem Punkt aufgetreten. Außer natürlich, wenn Sie vom Benutzer angegebene Daten wie GET und POST verwenden. Diese sollten Sie vor der Verwendung desinfizieren und validieren. Dies ist etwas, was Sie mit einer dieser foreach-Schleifen tun könnten. Oder Sie könnten auch auschecken filter_input_array()und seine Cousins.

Ich weiß, ich persönlich würde die zweite Implementierung wegen mangelnder Lesbarkeit nicht verwenden. Zumindest bei der ersten und der dritten Implementierung können Sie visuell sehen, dass ein Wert geändert wird. Der zweite ist nicht ohne weiteres ersichtlich. Am effizientesten ist es jedoch. Ich habe sowohl den ersten als auch den dritten selbst verwendet, aber häufiger den dritten. Ich denke, es hat damit zu tun, in welcher Stimmung ich bin. Hoffe, das hilft, ich bin interessiert zu hören, was andere sagen müssen :)

"Argumente, die foreach gegeben werden, werden bei jeder Iteration deklariert. Daher rufen Sie array_keys () jedes Mal auf, wenn foreach eine Schleife durchläuft." Nein, Array_keys wird in diesem Zusammenhang nur einmal aufgerufen. (Vielleicht vor PHP5 hatte es dieses Verhalten, aber ich denke nicht, da dies sehr leicht in unendlichen Schleifen enden würde.) Corbin vor 8 Jahren 0
@ Corbin: ** Nö **, immer noch ein Problem. Glauben Sie mir nicht, führen Sie einige Tests durch und Sie werden es selbst sehen. PHP erstellt bei jeder Schleife ein Iterator-Objekt für den ersten Parameter, sodass es bei jeder Iteration `each ()` ausführen kann. Es wird neu erstellt, der Zeiger bleibt jedoch gleich. Wenn Sie also den Wert des Arrays durch Verweis geändert haben, ist er bei der nächsten Iteration sofort verfügbar. mseancole vor 8 Jahren 0
Kannst du ein Beispiel geben? Ich habe nur versucht, array_keys (na ja, 'f') zweimal aufzurufen, und konnte es nie schaffen. Corbin vor 8 Jahren 0
Möglicherweise relevant: http://stackoverflow.com/questions/4043569/php-foreach-function-performance Corbin vor 8 Jahren 0
@ Corbin: Ich bin mir ehrlich gesagt nicht sicher. Ich habe gesehen, wie jemand anderes genau das Gegenteil behauptete, und meine Geschwindigkeitstests scheinen es zu beweisen. Das Foreach mit dem "neu erstellten" Array scheint immer länger zu dauern. Es wurde ein Test von über hundert Iterationen durchgeführt, und alle scheinen schlüssig zu sein, dass im Hintergrund etwas Extra ausgeführt wird. Ich schaue mich immer noch nach diesem Beitrag um, hoffentlich kann ich ihn finden. Ich muss anfangen, ein Repository dieser Posts zu führen ... mseancole vor 8 Jahren 0
Es ist leicht zu beweisen, dass die Funktion nicht immer wieder aufgerufen wird. Verwenden Sie einfach foreach (f ($ arr) als $ v) {} `mit einem` echo` in `f`. Wenn es jedoch tatsächlich einen Leistungsunterschied gibt, keine Ahnung, woran das liegt. Corbin vor 8 Jahren 0
Habe vor einer Minute einige sehr grobe Tests gemacht und die Ergebnisse sind ziemlich seltsam. foreach (f ($ x) ...) ist für kleine Arrays deutlich schneller und $ fx = f ($ x); foreach ($ fx ...) `ist bei großen Arrays schneller. Es muss ein paar seltsame Low-Level-Sachen laufen.>. <. Corbin vor 8 Jahren 0
Ich konnte diesen Beitrag nicht finden, aber wie Sie bei Ihren eigenen Geschwindigkeitstests gesehen haben, "läuft etwas". Während meine Argumentation falsch ist, ist es dennoch besser, die Erstellung des Arrays von der Schleife zu trennen. Oder zumindest tut es nichts weh. mseancole vor 8 Jahren 0
Sie vernachlässigen den Fall, bei dem es schneller geht, die Funktion zu integrieren. Corbin vor 8 Jahren 0
@ Corbin: Okay, endlich herausgefunden. Siehe Update oben ... mseancole vor 8 Jahren 0
"Der Overhead, den wir erleben, ist nicht von foreach, sondern von array_keys (), was einige Setup-Zeiten erfordert, die seine Leistung drastisch verringern." macht keinen Sinn (ist auch nicht drastisch), aber definitiv besser als vor dem Editieren :). (Tut mir leid, dass ich der 'Kerl' bin, es gibt nur eine Menge Fehlinformationen über ein paar Kanten von PHP, und das macht mich traurig: S.) Außerdem sollten Sie die Argumente, die foreach gegeben wurden, herausarbeiten. . Corbin vor 8 Jahren 0
Drastisch war vielleicht irreführend, ich meinte es so relativ. Ich verstehe nicht, dass das keinen Sinn machte, aber ich habe es editiert, um hoffentlich zu klären. Fahre fort, dieser Kerl zu sein. Ich hätte lieber, dass mich jetzt jemand erwischt hat, anstatt Jahre später, nachdem ich vielen Leuten falsche Informationen gegeben hatte. mseancole vor 8 Jahren 0
Ich nehme an, ich lese jetzt zurück, weil ich es im falschen Kontext gelesen habe. Ich denke, es ist jetzt viel klarer, aber ich denke, es war mir über das Nachdenken und Missverständnis zuvor. Obwohl der Absatz immer noch so klingt, als würde er für und foreachs Leistung statt für foreach (array_keys ($ arr) ...) `vergleichen, verglichen mit` $ keys = array_keys ($ arr); foreach ($ keys) `verglichen mit` foreach ($ array as ...) `.) Corbin vor 8 Jahren 0
Die Antwort könnte auch etwas irreführend für Leute sein, die die Kommentare seit "foreach (f () ...)" nicht gelesen haben, verglichen mit "$ vals = f ();" foreach ($ vals ...) `ist im Wesentlichen die gleiche Leistung, wohingegen` foreach (array_keys ($ array) ...) `und` foreach ($ array als $ k => $ v) `unterschiedliche Leistungen haben werden (nicht weil eines Funktionsaufrufs, aber aufgrund dessen, was die Funktion tut - im Grunde eine hackige Art und Weise, ein Sprachkonstrukt neu zu erstellen. Wenn Sie den array_keys-Ansatz verwenden, müssen die Schlüssel alle kopiert werden.) Corbin vor 8 Jahren 0
0
Nadir Sampaoli

Ich würde mit Ihrem zweiten Ansatz gehen:

foreach ($array as &$item) {
  $item = perform_changes_on($item);  
}

Oder noch besser:

function perform_changes_on(&$item) {
// ...
}

foreach ($array as &$item) {
  perform_changes_on($item);  
}
// ...

Die Arbeit an jedem Element (als Referenz) scheint die sicherste zu sein, da Sie nicht auf die Struktur des Arrays zugreifen, sondern auf dessen einzelne Elemente (was Sie jedoch möchten).