Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> [PHP]Dziwne zachowanie array_walk_recursive
vonski
post 27.02.2014, 23:38:05
Post #1





Grupa: Zarejestrowani
Postów: 292
Pomógł: 89
Dołączył: 27.12.2006
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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


--------------------
Zend Certified Engineer | Microsoft Certified Professional: Programming in HTML5 with JavaScript & CSS3 | Blog
Go to the top of the page
+Quote Post
irmidjusz
post 28.02.2014, 05:51:25
Post #2





Grupa: Zarejestrowani
Postów: 279
Pomógł: 60
Dołączył: 25.02.2012

Ostrzeżenie: (0%)
-----


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


--------------------
there is much to be learned
Go to the top of the page
+Quote Post
vonski
post 28.02.2014, 11:30:03
Post #3





Grupa: Zarejestrowani
Postów: 292
Pomógł: 89
Dołączył: 27.12.2006
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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


--------------------
Zend Certified Engineer | Microsoft Certified Professional: Programming in HTML5 with JavaScript & CSS3 | Blog
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 19.04.2024 - 14:26