1

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

)

2
  • Please include sample input and output so that we can better understand the question. Commented Jul 13, 2015 at 23:54
  • added the desired output and provided the input Commented Jul 14, 2015 at 1:01

2 Answers 2

3

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();
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

2

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

1 Comment

I am going to choose your answer because it's more complete, I do like the SPL answer as well, more OOP vs Alghoritms

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.