Reputation: 53241
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?
Upvotes: 6
Views: 18100
Reputation: 950
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',
]);
Upvotes: 0
Reputation: 1555
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
);
}
$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)
}
}
Upvotes: 0
Reputation: 78994
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));
Upvotes: 1
Reputation: 72226
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';
Upvotes: 4