Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP]Dziwne zachowanie array_walk_recursive
Forum PHP.pl > Forum > Przedszkole
vonski
Witam.
Natrafiłem dziś na dość dziwne zachowanie array_walk_recursive, mam nadzieję że ktoś mądrzejszy mnie oświeci co jest tu grane smile.gif
Generalnie chciałem napisać funkcję sumującą wszystkie elementy wielowymiarowej tablicy.
Mam np. taką tablicę:

  1. $a = array(
  2. 1, 2, 19,
  3. array(15, 20),
  4. array(12, 40, array(10, 20, array(1, 2, 3), 10), 23),
  5. 1);


I początkowo napisałem takie coś:

  1. $sum = 0;
  2. array_walk_recursive($a, function($val, $key, &$sum) {
  3. $sum += $val;
  4. echo $sum . '<br>';
  5. }, $sum);
  6.  
  7. echo 'wynik: ' . $sum;


Rezultat okazał się, delikatnie mówiąc, zaskakujący:
  1. 1
  2. 3
  3. 22
  4. 37
  5. 57
  6. 34
  7. 74
  8. 84
  9. 104
  10. 105
  11. 107
  12. 110
  13. 114
  14. 97
  15. 23
  16. wynik: 0


Z powyższego wynika, że array_walk_recursive przy napotkaniu kolejnej tablicy przekazuje wcześniej obliczony parametr $sum dalej co jest jak najbardziej właściwym zachowaniem. Problem w tym, że jak "wychodzi" z danej tablicy i "wchodzi" do kolejnej na tym samym poziomie zagnieżdżenia, to przekazuje ten sam $sum, co do pierwszej tablicy. Ciężko to opisać, ale jak się przyjrzycie to zrozumiecie o co mi chodzi smile.gif Tak jakby na każdym poziomie zagnieżdżenia była jedna lokalna wartość $sum obliczona na podstawie wcześniejszych elementów. Najlepiej widać to jak się popatrzy na pierszą, drugą, trzecią i ostatnią liczbę z przykładowego rezultatu (nie biorąc pod uwagę "wynik:0" - o tym zaraz smile.gif ). Czyli mamy "1" (pierwszy element tablicy), potem jest "3" (1 + 2), potem "22" (1 + 2 + 19), natomiast na końcu jest "23", bo ostatnim elementem tablicy jest "1" (czyli 1 + 2 + 19 + 1 = 23). Czy ja czegoś nie rozumiem? smile.gif Czy to jest bug?

Druga sprawa to: "wynik:0". No właśnie, dlaczego? Przecież parametr $sum przekazywany jest do funkcji przez referencję, więc teoretycznie obie zmienne odwołują się do tej samej zmiennej. Poza tym widać wyraźnie, że parametr $sum jest modyfikowany podczas wykonywania array_walk_recursive, a mimo to na końcu ma on nadal wartość "0".
Generalnie problem rozwiązałem w ten sposób:

  1. $sum = 0;
  2. array_walk_recursive($a, function($val, $key) use(&$sum) {
  3. $sum += $val;
  4. });


Jednak interesuje mnie to arcydziwne zachowanie funkcji w pierwszym przykładzie. Czy faktycznie jest tak późno, że nie widzę czegoś oczywistego? smile.gif
irmidjusz
Dokładnie tak jak zaobserwowałeś, chodzi o to, że array_walk_recursive jest jakby uruchamiane od nowa na każdy nowy poziom zagłębienia tablicy, od nowa wywołuje iterację po aktualnym poziomie z zdefiniowaną funkcją oraz aktualną, istniejącą w danym zasięgu widoczności wartością $sum. Na każdy taki nowy poziom iteracji brana jest aktualna wartość $sum i zostaje użyta przy iteracji, a dopiero w danej iteracji (na jednym poziomie zagłębienia) pomiędzy kolejnymi wywołaniami funkcja anonimowa otrzymuje referencję do $sum.

Widać to wyraźnie przy takim zapisie:

  1. $sum = 0;
  2.  
  3. $func = function($val, $key, &$sum){
  4. echo 'current sum: ' . $sum . '<br>';
  5. $sum += $val;
  6. echo $sum . '<br>';
  7. };
  8.  
  9. array_walk_recursive($a, $func, $sum);


Niestety, zapis:
  1. array_walk_recursive($a, $func, &$sum);

jest niemożliwy (powoduje błąd). Stąd wywołanie:
  1. array_walk_recursive($a, $func, $sum);
daje takie dziwne wyniki i końcowa wartość $sum = 0 (bo w początkowym zasięgu widoczności wartość $sum się nie zmieniła - tylko wartość $sum jest przekazywana do iteracji, nie referencja).

No a ostatni przykład robi co chcesz, bo właśnie do tego jest to use(&$sum) - aby można użyć referencji. Dobrze jego zachowanie widać po tym:
  1. $sum = 0;
  2.  
  3. $func = function($val, $key) use(&$sum) {
  4. echo 'current sum: ' . $sum . '<br>';
  5. $sum += $val;
  6. echo $sum . '<br>';
  7. };
  8.  
  9. array_walk_recursive($a, $func);


Swoją drogą, tak samo działa:
  1. $sum = 0;
  2.  
  3. $func = function($val, $key) {
  4. global $sum;
  5. echo 'current sum: ' . $sum . '<br>';
  6. $sum += $val;
  7. echo $sum . '<br>';
  8. };
  9.  
  10. array_walk_recursive($a, $func);

wink.gif
vonski
Cytat
array_walk_recursive jest jakby uruchamiane od nowa na każdy nowy poziom zagłębienia tablicy, od nowa wywołuje iterację po aktualnym poziomie z zdefiniowaną funkcją oraz aktualną, istniejącą w danym zasięgu widoczności wartością $sum


Właśnie to jest dość dziwne dla mnie. Tzn. nie wiem w czym to w sumie pomaga, jak dla mnie bardziej logiczne byłoby przekazywanie referencji do zmiennej przy każdej iteracji, a nie przy zmianie poziomów zagnieżdżenia. Generalnie dotychczas myślałem o array_walk_recursive (i o innych tego typu funkcjach), że, najprościej mówiąc, spłaszcza ona tymczasowo tablicę po której iteruje. Wychodzi na to, że jest inaczej. Ogólnie mówiąc, dla mnie to działanie jest lekko "buggy" smile.gif

Cytat
o a ostatni przykład robi co chcesz, bo właśnie do tego jest to use(&$sum) - aby można użyć referencji


Tutaj też nie dokońca rozumiem, dlaczego w funkcji anonimowej nie można po prostu użyć referencji. Chociaż z drugiej strony "może" to rozumiem. Generalnie na początku kierowałem się tym, że np. w funkcji preg_match() podajesz parametr, powiedzmy $matches, który będzie zawierał informację o poszczególnych dopasowaniach. I to działa. Z tym że teraz właśnie do mnie doszło, że przecież to jest tak naprawdę zwykła funkcja biorąca jeden z parametrów przez referencję, podczas gdy w przypadku array_walk_recursive w całym procesie "pośredniczy" jeszcze callback. I pewnie właśnie dlatego jest tak jak mówisz - po to wymyślono użycie "use". Właściwie jak to napisałem teraz, to to zrozumiałem smile.gif

Wniosek: "there is much to be learned" - masz rację smile.gif
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.