6

I need to iterate over an array that looks like this:

$myArray = array( 'key1' => array('subkey' => 'subvalue'), 'key2' => array('subkey' => 'subvalue'), ) 

Into each nested associative array, I want to add a key-value pair based on the outside key, like this:

$myNewArray = array( 'key1' => array('subkey' => 'subvalue', 'newkey' => 'only for key1'), 'key2' => array('subkey' => 'subvalue'), ) 

Ideally, I'm looking for something like:

$myNewArray = array_map(function($key, $value) { if ($key == 'key1') { $value['newkey'] = 'only for key1'; } return $value; }, $myArray); 

However, that obviously doesn't work as callback isn't given two parameters but only one. I could do something like this:

$myNewArray = array_map(function($key, $value) { if ($key == 'key1') { $value['newkey'] = 'only for key1'; } return WHAT?? }, array_keys($myArray), $myArray); 

However, what do I return here? It seems to always construct a new array, i.e. discarding my string keys (key1 and key2), while a single-array array_map() keeps them.

I can use array_walk() but it has a rather strange API (flipped parameter order, array passed by reference etc.) so I would generally prefer if this could be achieved using array_map(). Can it?

5 Answers 5

4

I'm afraid array_walk() IS the way to do this.

If you don't like array_walk() and insist on doing it with array_map(), well, it's possible. It involves using also array_keys(), array_values() and array_combine(), it is long and ugly but doable:

$myNewArray = array_combine( array_keys($myArray), array_map( function($key, $value) { if ($key == 'key1') { $value['newkey'] = 'only for key1'; } return $value; }, array_keys($myArray), array_values($myArray) // can omit array_values() and use $myArray ) ); 

You can also do it using array_reduce() but it's the same mess:

$myNewArray = array_reduce( array_keys($myArray), function (array $carry, $key) use ($myArray) { $value = $myArray[$key]; if ($key == 'key1') { $value['newkey'] = 'only for key1'; } $carry[$key] = $value; return $carry; }, array() ); 

I hope you have a condition more complex than $key == 'key1' because only for this it is not worth it writing complex traversal. Isn't it easier to just access the right element and modify it?

$myArray['key1']['newkey'] = 'only for key1'; 
Sign up to request clarification or add additional context in comments.

1 Comment

Yes, the transformation is far more complex, I just gave the minimal example that shows the challenge. Thanks for your answer, the first method actually doesn't look too ugly to me, though the array_walk() method will certainly be shorter. I just don't like passing arrays by reference at all.
1

array_map only passes the value not the key. array_walk is very similar to the array_map. Just define the value as a reference. It takes the key where array_map does not. ALso, this modifies $myArray:

array_walk($myArray, function(&$value, $key) { if ($key == 'key1') { $value['newkey'] = 'only for key1'; } }); 

From the PHP manual, there does seem to be an array_map method using array_keys as another array:

$myNewArray = array_combine(array_keys($myArray), array_map(function($key, $value) { if ($key == 'key1') { $value['newkey'] = 'only for key1'; } return $value; }, array_keys($myArray), $myArray)); 

4 Comments

Yes, this works, however, as your example shows, the API is confusing and it's easy to make a mistake - $myNewArray in your example will be true :) But I get it, thanks.
I just spotted that when I was working with array_map. Edited.
Your second example will return an array of associative arrays, which is different from just associative array.
Fixed, just got longer and longer... Whether it's confusing or not, array_walk is the proper and standard way.
0

Array transformation function variant, supporting keys:

/** * Transform array elements. * * @param array $a Original array. * @param callable(V, K, $a): V|null $vTransformer Value transformation function. * @param callable(K, V, $a): K|null $kTransformer Key transformation function. * * @template K Array key * @template V Array value * * @return array Transformed array. */ function transformArray(array $a, ?callable $vTransformer = null, ?callable $kTransformer = null): array { $arrayKeys = array_keys($a); $arrayValues = array_values($a); return array_combine( is_callable($kTransformer) ? array_map(fn ($k) => $kTransformer($k, $a[$k], $a), $arrayKeys, $arrayValues) : $arrayKeys, is_callable($vTransformer) ? array_map(fn ($k) => $vTransformer($a[$k], $k, $a), $arrayKeys, $arrayValues) : $arrayValues ); } 

Example

$a = [ 'k1' => 100, 'k2' => 101, ]; $b = transformArray($a, fn ($v) => $v + 10 ); $c = transformArray($a, null, fn ($k) => "k-$k" ); $d = transformArray($a, fn ($v) => $v + 10, fn ($k) => "k-$k" ); var_dump([ 'a' => $a, 'b' => $b, 'c' => $c, 'd' => $d, ]); 
array(2) { ["a"] => array(2) { ["k1"] => int(100) ["k2"] => int(101) } ["b"]=> array(2) { ["k1"] => int(110) ["k2"] => int(111) } ["c"]=> array(2) { ["k-k1"] => int(100) ["k-k2"] => int(101) } ["d"]=> array(2) { ["k-k1"] => int(110) ["k-k2"] => int(111) } } 

Comments

0

Use a custom fucntion.

I know I am late to the party but, the answer is so simple if you can define your own function.

function array_map_kv(array $array, callable $callback): array { $newArray = []; foreach ($array as $key => $value) { $newArray += $callback($key, $value); } return $newArray; } 

Super easy to understand and use, just return an array in your callback:

//DEMO of associative array map function $oldArray = [ 'Chicky!' => null, 'not part of the song' => 'blah, blah, blah', 'My name is Cha-Cha!' => 'Clap, clap, cha-cha-cha' ]; $newArray = array_map_kv($oldArray, function ($key, $value) { //modify key or value or both at once. if (empty($value)) return ['My name is Chicky!' => 'Chicky, Chicky, Chicky']; // you can exclude entries giving you the power of array_filter at the same time 😉. if ($key === 'not part of the song') return []; // or change nothing. return [$key => $value]; }); //the expected result. assert($newArray == [ 'My name is Chicky!' => 'Chicky, Chicky, Chicky', 'My name is Cha-Cha!' => 'Clap, clap, cha-cha-cha', ]); 

Comments

0

Not the best approach performance wise but if you still want to do it with array_map, you can just pass the keys as the third parameter instead of second using array_keys . And then return the value itself.

$myArray = array( 'key1' => array('subkey' => 'subvalue'), 'key2' => array('subkey' => 'subvalue'), ); $myNewArray = array_map(function($value,$key){ if($key == 'key1'){ $value['newkey'] = 'only for key1'; } return $value; },$myArray,array_keys($myArray)); 

Here's the result with print_r($myNewArray)

Array ( [0] => Array ( [subkey] => subvalue [newkey] => only for key1 ) [1] => Array ( [subkey] => subvalue ) ) 

Simple enough for small arrays but consider the alternatives for better performance.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.