Iterate multidimensional Array recursively and ret

2019-08-14 14:52发布

问题:

I am trying to write a snippet that takes an multidimensional array and inserts some keys at the same level where a named search key is found. I don't have to rely on the structure of the array (but will be at most 5 levels) I can't use passing by reference so a traditional recurring function won't help with that approach.

I have 2 options: SPL or a recursion that re-constructs the array and alters it along the way

with SPL I can't seem to Insert a new value..

            $a= new \ArrayObject($priceConfig);
            $array = new \RecursiveArrayIterator($a);
            $iterator = new \RecursiveIteratorIterator($array, \RecursiveIteratorIterator::SELF_FIRST);
            foreach ($iterator as $key => $value) {
                if (is_array($value) && $key == 'prices') {
                    $iterator->offsetSet('myPrice',['amount'=>'1.00']);
                }
            }

            print_r($a->getArrayCopy());

It won't insert the new key at the desired level but it does loop through the array.. what am I missing?

The recursive function that reconstructs the array and insert new values at my key search in the nested array works, but I would like to use the Iterators to do that..

             function recursive( $input, $searchKey, $key=null) {
                $holder = array();
                if(is_array( $input)) {
                    foreach( $input as $key => $el) {
                        if (is_array($el)) {
                            $holder[$key] = recursive($el, $searchKey, $key);
                            if ($key == $searchKey) {
                                $holder[$key]['inertedPrice'] = "value";
                            }
                        } else {
                            $holder[$key] = $el;
                        }
                    }
                }
                return $holder;
            }

INPUT (will always have some "prices key and structure at X level")

    [1] => Array
        (
            [1] => Array
                (
                    [prices] => Array
                        (
                            [onePrice] => Array( [amount] => 10)
                            [finalPrice] => Array ([amount] => 10)
                        )
                    [key1] => value2
                    [key2] => value2
                )

            [2] => Array
                (
                    [prices] => Array
                        (
                            [otherPrice] => Array([amount] => 20)
                            [finalPrice] => Array([amount] => 20)
                        )
                    [key] => value
                )
        )
)

Output

[1] => Array
    (
        [1] => Array
            (
                [prices] => Array
                    (
                        [onePrice] => Array( [amount] => 10)
                        [finalPrice] => Array ([amount] => 10)
                        [INSERTEDPrice] => Array([amount] => value)
                    )
                [key1] => value2
                [key2] => value2
            )

        [2] => Array
            (
                [prices] => Array
                    (
                        [otherPrice] => Array([amount] => 20)
                        [finalPrice] => Array([amount] => 20)
                        [INSERTEDPrice] => Array([amount] => )
                    )
                [key] => value
            )
    )

)

回答1:

You could approach the problem using basic tools like foreach loops and recursion fairly easily. Here is such a solution.

function mergeWithKey($targetKey, $new, array $array) {
  foreach ($array as $key => $value) {
    if ($key === $targetKey) {
      $array[$key] = array_merge($array[$key], $new);
    }
    elseif (is_array($value)) {
      $array[$key] = mergeWithKey($targetKey, $new, $value); 
    }
  }
  return $array;
}

// Example
$output = mergeWithKey('prices', array('INSERTEDPrice' => 'value'), $input);

Simply, as we iterate over the array if we find the key we're looking for then we merge in the new price. If we instead find a sub-array then we merge the new price into that sub-array.

I gave some effort to generalise the function by keeping the key "prices" as a parameter. This is probably still a wonky function unlikely to see reuse.

With a couple common algorithms you can whip up this function quite nicely, and the bonus is you can reuse the algorithms. One is to map the array with both key and value, and the other is to flatten a 2D array to a 1D array.

function arrayMapWithKey(callable $f, array $array) {
  $out = array();
  foreach ($array as $key => $value) {
    $out[$key] = $f($key, $value);
  }
  return $out;
}

function concat(array $array) {
  if (empty($array)) {
    return array(); 
  }
  else {
    return call_user_func_array('array_merge', $array);
  }
}

These definitions enable to you write an alternative solution.

function addPrice($name, $price, $data) {
  return concat(arrayMapWithKey(
    function ($k, $v) use ($name, $price) {
      if ($k === 'prices') {
        return array($k => array_merge($v, array($name => $price)));
      }
      elseif (is_array($v)) {
        return array($k => addPrice($name, $price, $v));  
      }
      else {
        return array($k => $v);
      }
    },
    $data
  ));
}

Another formulation for arrayMapWithKey is to instead copy the keys with the value and then using a regular array_map.

function arrayWithKeys(array $array) {
  $out = array();
  foreach ($array as $key => $value) {
    // in PHP arrays are often used as tuples,
    // and here we have a 2-tuple.
    $out[] = array($key, $value);
  }
  return $out;
}


回答2:

You can do this with iterators by extending the RecursiveArrayIterator with your custom iterator logic:

class Foo extends RecursiveArrayIterator
{
    public function getChildren()
    {
        if ($this->key() == 'prices') {
            return new self(array_merge($this->current(), ['foo' => 'bar']));
        } else {
            return parent::getChildren();
        }
    }
}