2011-12-21 11 views
6

Od jakiegoś czasu używam "tradycyjnej" funkcji rekursywnej do spłaszczania wielowymiarowych tablic taki jakFunkcja pass-by-reference nie działa z dodatkowymi parametrami dla array_walk_recursive, chyba że jest to przestarzałe odwołanie do czasu wywołania

$baseArray = array(array('alpha'), 
        array('beta','gamma'), 
        array(), 
        array(array('delta','epsilon'), 
         array('zeta',array('eta', 
              'theta' 
              ), 
          ), 
         ), 
        array('iota'), 
       ); 

na prostą tablicę 1-d.

Ostatniej nocy pomyślałem, że przyjrzę się użyciu array_walk_recursive(), aby sprawdzić, czy mogę sprawić, że będzie on bardziej wydajny i czyściejszy.

Moja pierwsza próba nie była bardzo udana:

function flattenArray($arrayValue, $arrayKey, &$flatArray) { 
    $flatArray[] = $arrayValue; 
} 


$flattenedArray = array(); 
array_walk_recursive($baseArray,'flattenArray',$flattenedArray); 

myślałem powinno działać, ale wszystko, co mam była seria błędów:

Warning: Cannot use a scalar value as an array in C:\xampp\htdocs\arrayTest.php on line 16 

i wynikiem:

array(0) { } 

Podpowiedzi typów w funkcji flattenArray() dały mi

Catchable fatal error: Argument 3 passed to flattenArray() must be an array, integer given in C:\xampp\htdocs\arrayTest.php on line 16 

Korzystanie zamknięcie dało identyczne wyniki

Jedynym sposobem mogę zmusić go do pracy (bez uciekania się do korzystania z globalną lub statyczną dla mojego flattenedArray) był za pomocą call-czas pass-by-reference:

function flattenArray($arrayValue, $arrayKey, $flatArray) { 
    $flatArray[] = $arrayValue; 
} 


$flattenedArray = array(); 
array_walk_recursive($baseArray,'flattenArray',&$flattenedArray); 

która produkuje poprawny wynik

array(9) { [0]=> string(5) "alpha" [1]=> string(4) "beta" [2]=> string(5) "gamma" [3]=> string(5) "delta" [4]=> string(7) "epsilon" [5]=> string(4) "zeta" [6]=> string(3) "eta" [7]=> string(5) "theta" [8]=> string(4) "iota" } 

ale daje mi nie-nieoczekiwane ostrzeżenie

Deprecated: Call-time pass-by-reference has been deprecated in C:\xampp\htdocs\arrayTest.php on line 22 

Wiem, PHP jest językiem, dziwaczne, ale to wydaje się być nieco ekstremalne. Dokumentacja wyraźnie pokazuje, że pierwszym parametrem dla array_walk_recursive jest pass-by-reference, ale wydaje się, że dodatkowe argumenty mogą być przekazywane tylko przez odniesienie w czasie połączenia. Dziwne!

wersja PHP 5.3.8

Wszelkie sugestie, w jaki sposób mogę korzystać array_walk_recursive spłaszczyć moją tablicę poprawnie, bez uzyskiwania nieaktualnych błędów (oprócz złożenia raportu o błędzie).

EDIT

PS:

Zdaję sobie sprawę, że mogę ominąć ten problem używając Zamknięcie:

$flattenedArray = array(); 
array_walk_recursive($baseArray, function($arrayValue, $arrayKey) use(&$flattenedArray){ $flattenedArray[] = $arrayValue; }); 
var_dump($flattenedArray); 

ale jak to jest wymagane dla biblioteki, która obecnie pozwala korzystać z PHP 5.2.0, nie jest praktycznym rozwiązaniem, aby użyć funkcja wymagająca znacznie późniejszej wersji PHP

Odpowiedz

6

Pomyśl o tym w ten sposób: Zdasz $flatArray do array_walk_recursivewartością. array_walk_recursive następnie przekazuje dalej argument przez odniesienie do swojej funkcji. Ale ponieważ została przekazana do wartości array_walk_recursive przez wartość, odniesienie będzie już wskazywać na inną wartość.

Wiem, może to wydawać się dziwnym ograniczeniem, ale kiedy o tym pomyślisz, jest to całkiem logiczne.

Nawiasem mówiąc, myślę, że przypadkowo znalazłeś inny problem z tym, to faktycznie wygląda na poważne uszkodzenie pamięci (spójrz na trzecie elementy tablicy drukowanej @http://codepad.viper-7.com/ZYNrNd). Zajrzę do tego.

Na marginesie, łatwym sposobem spłaszczyć stosuje RecursiveArrayIterator:

$flattenedArray = array(); 
foreach (new RecursiveIteratorIterator(
      new RecursiveArrayIterator($baseArray), 
      RecursiveIteratorIterator::LEAVES_ONLY 
     ) as $value) { 
    $flattenedArray[] = $value; 
} 
+0

Widzę logikę, chociaż nie jestem pewna, czy się z nią zgadzam ... argument arrayValue jest przekazywany do funkcji wywołania zwrotnego przez odniesienie, umożliwiając zmianę wartości tablicowych - zmieniacz funkcji (& $ arrayValue, $ arrayKey) { \t $ arrayValue. = 'TEST'; } array_walk_recursive ($ baseArray, 'changer'); var_dump ($ baseArray); –

+0

Problem z uszkodzeniem pamięci uderza mnie jako potencjalnie poważny, chociaż ... nie widziałem żadnego dowodu, dopóki go nie podświetliłeś. –

+0

Na pewno myślę, że powinna istnieć jakaś formalna uwaga oznaczająca, że ​​nie można użyć wywołania referencyjnego z dodatkowymi argumentami w funkcji wywołania zwrotnego jak dla funkcji call_user_func(), a nie tylko z komentarzem, który mógłbym dodać: –

1

Niezbyt pomocna na tym etapie.

Czytając docs PHP, znalazłem, że call_user_func() ma notatkę na to opis argumentów parametrów:

Zauważ, że parametry call_user_func() nie zapadają odniesienia.

, ale array_walk_recursive() nie ma takiego powiadomienia.

To może być czysto dokumentacyjny problem ... gdybym zobaczył podobne zawiadomienie w dokumentacji array_walk_recursive(), prawdopodobnie nie próbowałbym tego (chociaż ciekawi mogliby spróbować niezależnie, żeby zobaczyć co stało się).

Jednak nie rozumiem, dlaczego w obu przypadkach nie należy przyjmować argumentów typu "przekaż jako odniesienie" w definicji funkcji wywołania zwrotnego. To trochę przypomina krok w tył ... funkcja języka, która zadziałała (mimo że używa się odwołania do czasu połączenia), już tego nie robi, bez polegania na tej przestarzałej funkcji.

0

Pass od odniesienia w czasie rozmowy jest przestarzała:

http://uk3.php.net/manual/en/ini.core.php#ini.allow-call-time-pass-reference

To nie jest odnotowany w array_walk_recursive dokumentacji, ponieważ nie jest specyficzne dla tej funkcji.

Jedną z rzeczy, którą można zrobić, to przekazać nazwę obiektu i metody, a nie nazwę funkcji, jako wywołanie zwrotne i zachować stan spłaszczonej tablicy jako członka tego obiektu.

np.

class ArrayFlattener 
{ 
    //implementation and array state 
} 

$flattener = new ArrayFlattener(); 
array_walk_recursive($baseArray, array($flattener, 'flatten')); 

print_r($flattener->getFlattenedArray()); 
+1

Doceniam __call-time__ pass-by-reference jest przestarzała, ale starałem prostą przekazywany przez referencję, która nie jest nieaktualna, ale nie wydaje się działać, gdy używa się __additional__ argumentów w funkcja zwrotna. –

+0

Być może będę musiał użyć atrybutu class jako metody zastępczej .... będzie to używane w klasie, gdy tylko będę mógł ją uruchomić ... ale muszę upewnić się, że zainicjuję atrybut, gdy używam spłaszczenia metoda, która jest dodatkowym obciążeniem –

1

Od swoimi rozwiązaniami są wersji specyficznych (zgodnie z dokumentacją, PHP 5.2 nie powinien narzekać call-czas pass-by-reference), jedną z opcji jest stworzenie różnych skryptów zawierających każdą implementację, następnie inny skrypt warunkowo zawiera skrypt, który definiuje funkcję spłaszczania (i dowolny inny kod specyficzny dla wersji) w zależności od wersji PHP.

if (! defined('PHP_VERSION_ID')) { 
    $version = explode('.', PHP_VERSION); 
    define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); 
    if (PHP_VERSION_ID < 50207) { 
     define('PHP_MAJOR_VERSION', $version[0]); 
     define('PHP_MINOR_VERSION', $version[1]); 
     define('PHP_RELEASE_VERSION', $version[2]); 
    } 
} 
if (PHP_VERSION_ID < 50300) { 
    include_once('php-5.2-.php'); 
} else { 
    include_once('php-5.3+.php'); 
} 
+0

Musiałbym zobaczyć, jak to zadziała, to nie jest szczególnie czyste rozwiązanie, ale może dać mi trochę korzyści z wydajności Potrzebuję bez łamania funkcjonalności w różnych wersjach PHP .... jeśli (version_compare (PHP_VERSION, '5.3.0', '<')) {etc –

Powiązane problemy