2009-08-04 7 views
14

Ich vermute, dass ich hier etwas Dummes mache, aber ich bin verwirrt von dem, was wie ein einfaches Problem mit SPL scheint:Wie ändere ich Array-Schlüssel und Werte bei Verwendung eines RecursiveArrayIterator?

Wie ändere ich den Inhalt eines Arrays (die Werte in diesem Beispiel), mit a RecursiveArrayIterator/RecursiveIteratorIterator?

Mit dem folgenden Testcode kann ich den Wert innerhalb der Schleife mit getInnerIterator() und offsetSet() ändern und das modifizierte Array ausgeben, während ich innerhalb der Schleife bin.

Aber wenn ich die Schleife verlassen und das Array aus dem Iterator ausgeben, ist es wieder auf die ursprünglichen Werte. Was ist los?

$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

Sieht aus wie dies ein Fehler ist, dass Sie auf http://bugs.php.net/ Datei sollte – null

Antwort

2

Sieht aus wie getInnerIterator ein kopieren des Unter Iterator erzeugt.

Vielleicht gibt es eine andere Methode? (Bleiben Sie dran ..)


Update: nachdem eine Zeit lang bei ihm Hacking, und in drei anderen Ingenieuren ziehen, sieht es nicht wie PHP gibt Ihnen die Möglichkeit, die Werte der subIterator zu verändern.

Sie können immer den alten Stand verwenden von:

<?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; 
     } 
    } 
} 
?> 

ODER

<?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

ich glaube nicht, es ist so einfach wie getInnerIterator eine Kopie zu erstellen, da '$ cRecursiveIter -> getArrayCopy() 'innerhalb der Schleife gibt den modifizierten Wert –

0

Ich weiß, dass dies Ihre Frage nicht direkt beantwortet, aber es ist keine gute Praxis, das Objekt zu modifizieren unter Iteration beim Iterieren darüber.

+4

Are yo Bist du sicher? Die 'RecursiveArrayIterator'-Dokumente sagen" Dieser Iterator ermöglicht das Deaktivieren und Ändern von Werten und Schlüsseln beim Iterieren über Arrays und Objekte ... ". –

0

Könnte es passieren, dass wir nach Referenz oder nach Wert weiterleiten?

Zum Beispiel versuchen zu ändern:

$cArray = new ArrayObject($aNestedArray); 

zu:

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

Nein, das hilft nicht. Für das, was es wert ist, ist die Verwendung eines Referenz-foreach-Wertes auch für Iteratoren nicht erlaubt - dh dies verursacht einen fatalen Fehler: 'foreach ($ cRecursiveIter als & $ iVal)' –

5

Es scheint, dass Werte im Klar Arrays nicht veränderbar sind, weil sie nicht unter Bezugnahme auf den Konstruktor von ArrayIterator weitergegeben (RecursiveArrayIterator erbt seine offset*() Methoden aus dieser Klasse, siehe SPL Reference). So arbeiten alle Aufrufe an offsetSet() auf einer Kopie des Arrays.

Ich denke, sie entschieden Call-by-Reference zu vermeiden, weil es in einer objektorientierten Umgebung nicht viel Sinn macht (z. B. bei der Übergabe von Instanzen ArrayObject, die der Standardfall sein sollte).

Einige weitere Code dies zu verdeutlichen:

$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

Nicht die Iterator-Klassen (die scheinen Kopieren Daten auf dem RecursiveArrayIterator::beginChildren() statt vorbei Referenz.)

Sie können Folgendes erreichen verwenden, was Sie

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); 

oder verwenden create_function() statt der Schaffung der drop_200 Funktion, aber die Leistung kann variieren mit der create_function und Speichernutzung möchten.

+0

Nur eine Stunde mit diesen blutigen SPL-Iterator-Klassen verschwendet - wahrscheinlich nicht zum ersten Mal - wenn 'array_walk_recursive' den Trick perfekt macht, und mit weniger Instantiierung/LOC. Da ist eine Lektion, denke ich ... Wie auch immer, danke! –

+0

Dies scheint der zugehörige Bug-DB-Eintrag zu sein: https://bugs.php.net/bug.php?id=68682 – powtac

+0

Dies ist nicht die ideale Lösung, denn in rekursiv kann man den Datentyp der Elemente nicht überprüfen . Zweitens ist es besser, Iterator zu verwenden, weil es mehr oop ist. – schellingerht

1

Sie müssen getSubIterator in der aktuellen Tiefe aufrufen, verwenden Sie offsetSet in dieser Tiefe, und tun Sie dasselbe für alle Tiefen, die den Baum zurückgehen.

Dies ist wirklich nützlich für die Durchführung von unbegrenzten Ebenen Array Merge und Ersetzungen, auf Arrays oder Werte innerhalb von Arrays. Leider funktioniert array_walk_recursive nicht in diesem Fall, da diese Funktion nur Blattknoten besucht .. so wird der 'replace_this_array' Schlüssel in $ Array unten nie besucht werden.

Als Beispiel alle Werte innerhalb eines Arrays unbekannten Ebenen tief, zu ersetzen, sondern nur diejenigen, die einen bestimmten Schlüssel enthalten, würden Sie folgendes tun:

$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

Löst das Problem nicht für mich. – schellingerht

1

das Array zunächst auf ein Objekt konvertieren und es wie erwartet funktioniert ..

$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

Danke! Das funktioniert! – schellingerht