2009-08-04 9 views
14

Ho il sospetto che sto facendo qualcosa di stupido qui, ma sono confuso da quello che sembra un problema semplice con SPL:Come si alterano le chiavi ei valori dell'array mentre si utilizza RecursiveArrayIterator?

Come faccio ho modificato il contenuto di un array (i valori in questo esempio), utilizzando un RecursiveArrayIterator/RecursiveIteratorIterator?

Utilizzando il seguente codice di test, è possibile modificare il valore all'interno del loop utilizzando getInnerIterator() e offsetSet() e scaricare l'array modificato mentre sono all'interno del ciclo.

Ma quando esco dal ciclo e scarico la matrice dall'iteratore, si torna ai valori originali. Cosa sta succedendo?

$aNestedArray = array(); 
$aNestedArray[101] = range(100, 1000, 100); 
$aNestedArray[201] = range(300, 25, -25); 
$aNestedArray[301] = range(500, 0, -50); 

$cArray = new ArrayObject($aNestedArray); 
$cRecursiveIter = new RecursiveIteratorIterator(new RecursiveArrayIterator($cArray), RecursiveIteratorIterator::LEAVES_ONLY); 

// Zero any array elements under 200 
while ($cRecursiveIter->valid()) 
{ 
    if ($cRecursiveIter->current() < 200) 
    { 
     $cInnerIter = $cRecursiveIter->getInnerIterator(); 
     // $cInnerIter is a RecursiveArrayIterator 
     $cInnerIter->offsetSet($cInnerIter->key(), 0); 
    } 

    // This returns the modified array as expected, with elements progressively being zeroed 
    print_r($cRecursiveIter->getArrayCopy()); 

    $cRecursiveIter->next(); 
} 

$aNestedArray = $cRecursiveIter->getArrayCopy(); 

// But this returns the original array. Eh?? 
print_r($aNestedArray); 
+0

Sembra questo un bug si dovrebbe file sul http://bugs.php.net/ – null

risposta

2

Sembra getInnerIterator crea una copia del sub-iteratore.

Forse esiste un metodo diverso? (Restate sintonizzati ..)


Aggiornamento: dopo l'hacking a esso per un po ', e tirando in 3 altri ingegneri, non sembra come PHP offre un modo per alterare i valori del subIterator.

si può sempre utilizzare il vecchio stand by:

<?php 
// Easy to read, if you don't mind references (and runs 3x slower in my tests) 
foreach($aNestedArray as &$subArray) { 
    foreach($subArray as &$val) { 
     if ($val < 200) { 
      $val = 0; 
     } 
    } 
} 
?> 

O

<?php 
// Harder to read, but avoids references and is faster. 
$outherKeys = array_keys($aNestedArray); 
foreach($outherKeys as $outerKey) { 
    $innerKeys = array_keys($aNestedArray[$outerKey]); 
    foreach($innerKeys as $innerKey) { 
     if ($aNestedArray[$outerKey][$innerKey] < 200) { 
      $aNestedArray[$outerKey][$innerKey] = 0; 
     } 
    } 
} 
?> 
+1

io non credo che sia così semplice come getInnerIterator creazione di una copia, in quanto '$ cRecursiveIter -> getArrayCopy() 'all'interno del ciclo dà il valore modificato –

0

So che questo non risponde direttamente alla tua domanda, ma non è una buona pratica per modificare l'oggetto sotto iterazione mentre si itera su di essa.

+4

Are yo ne sei sicuro? I documenti 'RecursiveArrayIterator' dicono" Questo iteratore permette di disinserire e modificare valori e chiavi mentre si itera su Array e Oggetti ... ". –

0

Potrebbe passare al passaggio per riferimento rispetto al passaggio per valore?

Ad esempio provare a cambiare:

$cArray = new ArrayObject($aNestedArray); 

a:

$cArray = new ArrayObject(&$aNestedArray); 
+0

No, questo non aiuta. Per quello che vale, l'uso di un valore di riferimento foreach non è consentito neanche per gli iteratori - ciò causa un errore fatale: 'foreach ($ cRecursiveIter as & $ iVal)' –

5

Sembra che i valori nelle matrici semplici non sono modificabili perché non possono essere passate per riferimento al costruttore di ArrayIterator (RecursiveArrayIterator eredita i suoi metodi offset*() da questa classe, vedere SPL Reference). Quindi tutte le chiamate a offsetSet() funzionano su una copia dell'array.

Suppongo che abbiano scelto di evitare la chiamata per riferimento perché non ha molto senso in un ambiente orientato agli oggetti (ad esempio quando si superano le istanze di ArrayObject, che dovrebbe essere il caso predefinito).

Alcuni più codice per illustrare questo:

$a = array(); 

// Values inside of ArrayObject instances will be changed correctly, values 
// inside of plain arrays won't 
$a[] = array(new ArrayObject(range(100, 200, 100)), 
      new ArrayObject(range(200, 100, -100)), 
      range(100, 200, 100)); 
$a[] = new ArrayObject(range(225, 75, -75)); 

// The array has to be 
//  - converted to an ArrayObject or 
//  - returned via $it->getArrayCopy() 
// in order for this field to get handled properly 
$a[] = 199; 

// These values won't be modified in any case 
$a[] = range(100, 200, 50); 

// Comment this line for testing 
$a = new ArrayObject($a); 

$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($a)); 

foreach ($it as $k => $v) { 
    // getDepth() returns the current iterator nesting level 
    echo $it->getDepth() . ': ' . $it->current(); 

    if ($v < 200) { 
     echo "\ttrue"; 

     // This line is equal to: 
     //  $it->getSubIterator($it->getDepth())->offsetSet($k, 0); 
     $it->getInnerIterator()->offsetSet($k, 0); 
    } 

    echo ($it->current() == 0) ? "\tchanged" : ''; 
    echo "\n"; 
} 

// In this context, there's no real point in using getArrayCopy() as it only 
// copies the topmost nesting level. It should be more obvious to work with $a 
// itself 
print_r($a); 
//print_r($it->getArrayCopy());
3

Non usando le classi Iterator (che sembrano essere la copia dati sulla RecursiveArrayIterator::beginChildren() invece di passaggio per riferimento.)

È possibile utilizzare le seguenti operazioni per ottenere quello che vuoi

function drop_200(&$v) { if($v < 200) { $v = 0; } } 

$aNestedArray = array(); 
$aNestedArray[101] = range(100, 1000, 100); 
$aNestedArray[201] = range(300, 25, -25); 
$aNestedArray[301] = range(500, 0, -50); 

array_walk_recursive ($aNestedArray, 'drop_200'); 

print_r($aNestedArray); 

o utilizzare create_function() invece di creare la funzione drop_200, ma la vostra situazione potrebbe essere diversa con l'uso create_function e della memoria.

+0

Hai appena perso un'ora con quelle maledette lezioni di Iterator SPL - probabilmente no per la prima volta - quando 'array_walk_recursive' fa il trucco perfettamente e con meno istanze/LOC. C'è una lezione lì, credo ... Comunque, grazie! –

+0

Questa sembra essere la relativa voce del bug DB: https://bugs.php.net/bug.php?id=68682 – powtac

+0

Questa non è la soluzione ideale, perché in ricorsiva non è possibile controllare il tipo di dati degli elementi . In secondo luogo, è meglio usare iteratore, perché è più oop. – schellingerht

1

È necessario chiamare getSubIterator alla profondità corrente, utilizzare offsetSet a quella profondità e fare lo stesso per tutte le profondità che risalgono la struttura.

Questo è veramente utile per eseguire un merge e sostituzioni di array a livello illimitato, su array o valori all'interno di array. Sfortunatamente, array_walk_recursive NON funzionerà in questo caso poiché tale funzione visita solo i nodi foglia .. quindi la chiave 'replace_this_array' in $ array non verrà mai visitata.

A titolo di esempio, per sostituire tutti i valori all'interno di un array livelli sconosciuti in profondità, ma solo quelli che contengono un certo tasto, si dovrebbe effettuare le seguenti operazioni:

$array = [ 
    'test' => 'value', 
    'level_one' => [ 
     'level_two' => [ 
      'level_three' => [ 
       'replace_this_array' => [ 
        'special_key' => 'replacement_value', 
        'key_one' => 'testing', 
        'key_two' => 'value', 
        'four' => 'another value' 
       ] 
      ], 
      'ordinary_key' => 'value' 
     ] 
    ] 
]; 

$arrayIterator = new \RecursiveArrayIterator($array); 
$completeIterator = new \RecursiveIteratorIterator($arrayIterator, \RecursiveIteratorIterator::SELF_FIRST); 

foreach ($completeIterator as $key => $value) { 
    if (is_array($value) && array_key_exists('special_key', $value)) { 
     // Here we replace ALL keys with the same value from 'special_key' 
     $replaced = array_fill(0, count($value), $value['special_key']); 
     $value = array_combine(array_keys($value), $replaced); 
     // Add a new key? 
     $value['new_key'] = 'new value'; 

     // Get the current depth and traverse back up the tree, saving the modifications 
     $currentDepth = $completeIterator->getDepth(); 
     for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) { 
      // Get the current level iterator 
      $subIterator = $completeIterator->getSubIterator($subDepth); 
      // If we are on the level we want to change, use the replacements ($value) other wise set the key to the parent iterators value 
      $subIterator->offsetSet($subIterator->key(), ($subDepth === $currentDepth ? $value : $completeIterator->getSubIterator(($subDepth+1))->getArrayCopy())); 
     } 
    } 
} 
return $completeIterator->getArrayCopy(); 
// return: 
$array = [ 
    'test' => 'value', 
    'level_one' => [ 
     'level_two' => [ 
      'level_three' => [ 
       'replace_this_array' => [ 
        'special_key' => 'replacement_value', 
        'key_one' => 'replacement_value', 
        'key_two' => 'replacement_value', 
        'four' => 'replacement_value', 
        'new_key' => 'new value' 
       ] 
      ], 
      'ordinary_key' => 'value' 
     ] 
    ] 
]; 
+0

Non risolve il problema per me. – schellingerht

1

Converte l'array in un oggetto prima e funziona come previsto ..

$array = [ 
     'one' => 'One', 
     'two' => 'Two', 
     'three' => [ 
      'four' => 'Four', 
      'five' => [ 
       'six' => 'Six', 
       'seven' => 'Seven' 
      ] 
     ] 
    ]; 

    // Convert to object (using whatever method you want) 
    $array = json_decode(json_encode($array)); 

    $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array)); 
    foreach($iterator as $key => $value) { 
     $iterator->getInnerIterator()->offsetSet($key, strtoupper($value)); 
    } 

    var_dump($iterator->getArrayCopy()); 
+0

Grazie! Funziona! – schellingerht

Problemi correlati