2009-08-04 20 views
14

Tôi nghi ngờ tôi đang làm điều ngu ngốc ở đây, nhưng tôi bị nhầm lẫn bởi những gì có vẻ như một vấn đề đơn giản với SPL:Làm cách nào để thay đổi các giá trị và khóa mảng trong khi sử dụng RecursiveArrayIterator?

Làm cách nào để sửa đổi nội dung của một mảng (giá trị trong ví dụ này), bằng cách sử dụng RecursiveArrayIterator/RecursiveIteratorIterator?

Sử dụng mã kiểm tra tiếp theo, tôi có thể thay đổi giá trị trong vòng lặp bằng cách sử dụng getInnerIterator()offsetSet() và kết xuất mảng đã sửa đổi trong khi tôi đang trong vòng lặp.

Nhưng khi tôi rời khỏi vòng lặp và kết xuất mảng từ trình lặp, nó trở về giá trị ban đầu. Chuyện gì vậy?

$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

Hình như đây là lỗi bạn nên nộp vào http://bugs.php.net/ – null

Trả lời

2

Hình như getInnerIterator tạo ra một sao chép của tiểu iterator.

Có thể có phương pháp khác? (Chơ ..)


Cập nhật: sau khi hack vào nó một thời gian, và kéo trong 3 kỹ sư khác, nó không giống như PHP cung cấp cho bạn một cách để thay đổi các giá trị của subIterator.

Bạn luôn có thể sử dụng chân đế cũ bởi:

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

HOẶC

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

tôi không nghĩ rằng nó đơn giản như getInnerIterator tạo một bản sao, kể từ '$ cRecursiveIter -> getArrayCopy() 'trong vòng lặp cung cấp giá trị sửa đổi –

0

Tôi biết điều này không trả lời câu hỏi của bạn trực tiếp, nhưng nó không phải là một thực hành tốt để sửa đổi các đối tượng dưới lặp lại trong khi lặp qua nó.

+4

Có phải yo u chắc chắn về điều đó? Các tài liệu 'RecursiveArrayIterator' nói" Trình lặp này cho phép bỏ đặt và sửa đổi các giá trị và khóa trong khi lặp qua mảng và đối tượng ... ". –

0

Nó có thể đi xuống để vượt qua tham chiếu và đi qua giá trị không?

Ví dụ hãy thử thay đổi:

$cArray = new ArrayObject($aNestedArray); 

tới:

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

Không, điều đó không hữu ích. Đối với những gì nó có giá trị, bằng cách sử dụng một tham chiếu foreach giá trị không được phép cho iterators hoặc - tức là điều này gây ra một lỗi nghiêm trọng: 'foreach ($ cRecursiveIter như & $ iVal)' –

5

Dường như giá trị trong mảng đơn giản là không thể thay đổi được bởi vì họ không thể được thông qua bằng cách tham chiếu đến các nhà xây dựng của ArrayIterator (RecursiveArrayIterator kế thừa các phương thức offset*() từ lớp này, xem SPL Reference). Vì vậy, tất cả các cuộc gọi đến offsetSet() hoạt động trên một bản sao của mảng. Tôi đoán họ đã chọn để tránh gọi tham chiếu bởi vì nó không có ý nghĩa nhiều trong môi trường hướng đối tượng (ví dụ: khi truyền các phiên bản ArrayObject, đây là trường hợp mặc định).

Một số mã khác để minh họa điều này:

$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

Không sử dụng các lớp Iterator (mà dường như sao chép dữ liệu trên RecursiveArrayIterator::beginChildren() thay vì đi qua bằng cách tham khảo.)

Bạn có thể sử dụng sau đây để đạt được những gì bạn muốn

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

hoặc sử dụng create_function() thay vì tạo ra các chức năng drop_200, nhưng mileage của bạn có thể thay đổi với create_function và bộ nhớ sử dụng.

+0

Chỉ cần lãng phí một giờ với những SPL Iterator đẫm máu lớp học - có lẽ không lần đầu tiên - khi 'array_walk_recursive' thực hiện thủ thuật một cách hoàn hảo, và với ít instantiation/LOC. Có một bài học ở đó tôi đoán ... Dù sao, cảm ơn! –

+0

Điều này dường như là mục nhập DB lỗi liên quan: https://bugs.php.net/bug.php?id=68682 – powtac

+0

Đây không phải là giải pháp lý tưởng nhất, bởi vì trong đệ quy bạn không thể kiểm tra kiểu dữ liệu của các phần tử . Thứ hai, tốt hơn là sử dụng trình vòng lặp, vì nó nhiều hơn oop. – schellingerht

1

Bạn cần gọi getSubIterator ở độ sâu hiện tại, sử dụng offsetSet ở độ sâu đó và thực hiện tương tự cho tất cả các độ sâu sẽ sao lưu cây.

Điều này thực sự hữu ích khi thực hiện hợp nhất mảng cấp và thay thế, trên mảng hoặc giá trị trong mảng. Thật không may, array_walk_recursive sẽ KHÔNG hoạt động trong trường hợp này vì chức năng đó chỉ truy cập các nút lá .. do đó, phím 'replace_this_array' trong mảng $ bên dưới sẽ không bao giờ được truy cập.

Như một ví dụ, để thay thế tất cả các giá trị trong một mảng mức chưa biết sâu, nhưng chỉ những có chứa một phím nào đó, bạn sẽ làm như sau:

$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

Không giải quyết được vấn đề cho tôi. – schellingerht

1

Chuyển đổi mảng đến một đối tượng đầu tiên và nó làm việc như mong đợi ..

$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

Cảm ơn! Những công việc này! – schellingerht

Các vấn đề liên quan