2009-08-04 19 views
14

मुझे लगता है मैं यहाँ कुछ बेवकूफ कर रहा हूँ, लेकिन मैं क्या एसपीएल के साथ एक सरल समस्या की तरह लगता है द्वारा उलझन में हूँ:रिकर्सिवएरे इटरेटर का उपयोग करते समय मैं सरणी कुंजियों और मानों को कैसे बदलूं?

मैं कैसे एक सरणी की सामग्री (इस उदाहरण में मान) संशोधित, एक का उपयोग कर RecursiveArrayIterator/RecursiveIteratorIterator?

अनुवर्ती परीक्षण कोड का उपयोग करके, मैं getInnerIterator() और offsetSet() का उपयोग करके लूप के भीतर मान को बदल सकता हूं, और जब मैं लूप के अंदर संशोधित सरणी को डंप कर सकता हूं।

लेकिन जब मैं लूप छोड़ देता हूं और सरणी को सरणी से डंप करता हूं, तो यह मूल मानों पर वापस आ जाता है। क्या हो रहा है? getInnerIterator एक कॉपी उप इटरेटर की बनाता है की तरह

$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

ऐसा लगता है कि यह एक बग आप http://bugs.php.net/ – null

उत्तर

2

लग रहा है।

शायद एक अलग विधि है? (देखते रहें ..)


अद्यतन: थोड़ी देर के लिए यह कम से हैकिंग, और 3 अन्य इंजीनियरों में खींचने के बाद, यह नहीं दिखता है जैसे पीएचपी आप subIterator के मूल्यों को बदलने के लिए एक रास्ता देती है।

तुम हमेशा पुराने स्टैंड से उपयोग कर सकते हैं:

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

या

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

मैं इसे के रूप में सरल getInnerIterator एक प्रति बनाने के रूप में है नहीं लगता पर दर्ज करनी चाहिए है, 'के बाद से $ cRecursiveIter -> लूप के भीतर getArrayCopy() 'संशोधित मान –

0

मैं जानता हूँ कि यह सीधे अपने सवाल का जवाब नहीं है, लेकिन इसके तहत वस्तु को संशोधित करने के एक अच्छा अभ्यास नहीं है इसके दौरान पुनरावृत्ति करते समय पुनरावृत्ति।

+4

देता है यो आप इसके बारे में निश्चित हैं? 'रिकर्सिवएरे इटरेटर' दस्तावेज़ कहते हैं, "यह इटरेटर एरे और ऑब्जेक्ट्स पर पुनरावृत्ति करते समय मूल्यों और कुंजियों को अनसेट और संशोधित करने की अनुमति देता है ..."। –

0

क्या यह मूल्य के आधार पर संदर्भ बनाम पास हो सकता है?

उदाहरण के लिए बदलने का प्रयास करें:

$cArray = new ArrayObject($aNestedArray); 

रहे हैं:

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

नहीं, यह मदद नहीं करता है। इसके लायक होने के लिए, संदर्भ फ़ोरैच मान का उपयोग करने वाले इटरेटर के लिए अनुमति नहीं है - यानी यह घातक त्रुटि का कारण बनता है: 'foreach ($ cRecursiveIter & $ iVal) ' –

5

ऐसा लगता है कि सादा सरणियों में मानों परिवर्तनीय क्योंकि वे ArrayIterator के निर्माता के संदर्भ द्वारा पारित नहीं किया जा सकता है नहीं कर रहे हैं (RecursiveArrayIterator इस वर्ग से offset*() विधियों को विरासत में मिला है, SPL Reference देखें)। तो सभी कॉल offsetSet() सरणी की एक प्रति पर काम करते हैं।

मुझे लगता है कि उन्होंने कॉल-बाय-रेफरेंस से बचने के लिए चुना है क्योंकि यह ऑब्जेक्ट उन्मुख वातावरण में अधिक समझ में नहीं आता है (i. ई। ArrayObject के उदाहरणों को पारित करते समय जो डिफ़ॉल्ट मामला होना चाहिए)।

कुछ और कोड इस वर्णन करने के लिए:

$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

इटरेटर वर्गों (जो नकल संदर्भ द्वारा पारित करने के बजाय RecursiveArrayIterator::beginChildren() पर डेटा होने लगते हैं उपयोग न करना।)

आप आप क्या चाहते हैं

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

प्राप्त करने या बजाय drop_200 समारोह बनाने की create_function() उपयोग करने के लिए निम्नलिखित का उपयोग कर सकते हैं, लेकिन अपने लाभ को create_function और स्मृति के उपयोग के साथ भिन्न हो सकते हैं।

+0

बस उन खूनी एसपीएल इटरेटर वर्गों के साथ एक घंटे बर्बाद हो गया - शायद नहीं पहली बार - जब 'array_walk_recursive' पूरी तरह से चाल करता है, और कम तत्काल/LOC के साथ। मुझे लगता है कि एक सबक है ... वैसे भी, धन्यवाद! –

+0

यह संबंधित बग डीबी प्रविष्टि प्रतीत होता है: https://bugs.php.net/bug.php?id=68682 – powtac

+0

यह सबसे आदर्श समाधान नहीं है, क्योंकि रिकर्सिव में आप तत्वों के डेटा प्रकार की जांच नहीं कर सकते । दूसरा, यह इटरेटर का उपयोग करना बेहतर है, क्योंकि यह अधिक ओओपी है। – schellingerht

1

आपको वर्तमान गहराई पर getSubIterator पर कॉल करने की आवश्यकता है, उस गहराई पर offsetSet का उपयोग करें, और पेड़ का बैक अप लेने वाली सभी गहराई के लिए ऐसा ही करें।

यह एरे के भीतर सरणी या मानों पर असीमित स्तर सरणी विलय और प्रतिस्थापन करने के लिए वास्तव में उपयोगी है। दुर्भाग्यवश, array_walk_recursive इस मामले में काम नहीं करेगा क्योंकि केवल पत्ती नोड्स पर जाता है .. इसलिए नीचे $ array में 'replace_this_array' कुंजी का दौरा नहीं किया जाएगा।

उदाहरण के लिए, एक सरणी अज्ञात स्तर नीचे के भीतर सभी मूल्यों को बदलने के लिए है, लेकिन केवल उन है कि एक निश्चित कुंजी होते हैं, तो आपको निम्न करना होगा:

$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

मेरे लिए समस्या का समाधान नहीं करता है। – schellingerht

1

पहले एक वस्तु को सरणी में कनवर्ट करें और यह अपेक्षा के अनुरूप काम करता है ..

$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

धन्यवाद! यह काम! – schellingerht