PHP's array_map including keys
Is there a way of doing something like this:
$test_array = array("first_key" => "first_value",
"second_key" => "second_value");
var_dump(array_map(function($a, $b) { return "$a loves $b"; },
array_keys($test_array),
array_values($test_array)));
But instead of calling array_keys
and array_values
, directly passing the $test_array
variable?
The desired output is:
array(2) {
[0]=>
string(27) "first_key loves first_value"
[1]=>
string(29) "second_key loves second_value"
}
Solution 1:
Not with array_map, as it doesn't handle keys.
array_walk does:
$test_array = array("first_key" => "first_value",
"second_key" => "second_value");
array_walk($test_array, function(&$a, $b) { $a = "$b loves $a"; });
var_dump($test_array);
// array(2) {
// ["first_key"]=>
// string(27) "first_key loves first_value"
// ["second_key"]=>
// string(29) "second_key loves second_value"
// }
It does change the array given as parameter however, so it's not exactly functional programming (as you have the question tagged like that). Also, as pointed out in the comment, this will only change the values of the array, so the keys won't be what you specified in the question.
You could write a function that fixes the points above yourself if you wanted to, like this:
function mymapper($arrayparam, $valuecallback) {
$resultarr = array();
foreach ($arrayparam as $key => $value) {
$resultarr[] = $valuecallback($key, $value);
}
return $resultarr;
}
$test_array = array("first_key" => "first_value",
"second_key" => "second_value");
$new_array = mymapper($test_array, function($a, $b) { return "$a loves $b"; });
var_dump($new_array);
// array(2) {
// [0]=>
// string(27) "first_key loves first_value"
// [1]=>
// string(29) "second_key loves second_value"
// }
Solution 2:
This is probably the shortest and easiest to reason about:
$states = array('az' => 'Arizona', 'al' => 'Alabama');
array_map(function ($short, $long) {
return array(
'short' => $short,
'long' => $long
);
}, array_keys($states), $states);
// produces:
array(
array('short' => 'az', 'long' => 'Arizona'),
array('short' => 'al', 'long' => 'Alabama')
)
Solution 3:
Here's my very simple, PHP 5.5-compatible solution:
function array_map_assoc(callable $f, array $a) {
return array_column(array_map($f, array_keys($a), $a), 1, 0);
}
The callable you supply should itself return an array with two values, i.e. return [key, value]
. The inner call to array_map
therefore produces an array of arrays. This then gets converted back to a single-dimension array by array_column
.
Usage
$ordinals = [
'first' => '1st',
'second' => '2nd',
'third' => '3rd',
];
$func = function ($k, $v) {
return ['new ' . $k, 'new ' . $v];
};
var_dump(array_map_assoc($func, $ordinals));
Output
array(3) {
["new first"]=>
string(7) "new 1st"
["new second"]=>
string(7) "new 2nd"
["new third"]=>
string(7) "new 3rd"
}
Partial application
In case you need to use the function many times with different arrays but the same mapping function, you can do something called partial function application (related to ‘currying’), which allows you to only pass in the data array upon invocation:
function array_map_assoc_partial(callable $f) {
return function (array $a) use ($f) {
return array_column(array_map($f, array_keys($a), $a), 1, 0);
};
}
...
$my_mapping = array_map_assoc_partial($func);
var_dump($my_mapping($ordinals));
Which produces the same output, given $func
and $ordinals
are as earlier.
NOTE: if your mapped function returns the same key for two different inputs, the value associated with the later key will win. Reverse the input array and output result of array_map_assoc
to allow earlier keys to win. (The returned keys in my example cannot collide as they incorporate the key of the source array, which in turn must be unique.)
Alternative
Following is a variant of the above, which might prove more logical to some, but requires PHP 5.6:
function array_map_assoc(callable $f, array $a) {
return array_merge(...array_map($f, array_keys($a), $a));
}
In this variant, your supplied function (over which the data array is mapped) should instead return an associative array with one row, i.e. return [key => value]
.
The result of mapping the callable is then simply unpacked and passed to array_merge
. As earlier, returning a duplicate key will result in later values winning.
n.b. Alex83690 has noted in a comment that using
array_replace
here in the stead ofarray_merge
would preserve integer keys.array_replace
does not modify the input array, so is safe for functional code.
If you are on PHP 5.3 to 5.5, the following is equivalent. It uses array_reduce
and the binary +
array operator to convert the resulting two-dimensional array down to a one-dimensional array whilst preserving keys:
function array_map_assoc(callable $f, array $a) {
return array_reduce(array_map($f, array_keys($a), $a), function (array $acc, array $a) {
return $acc + $a;
}, []);
}
Usage
Both of these variants would be used thus:
$ordinals = [
'first' => '1st',
'second' => '2nd',
'third' => '3rd',
];
$func = function ($k, $v) {
return ['new ' . $k => 'new ' . $v];
};
var_dump(array_map_assoc($func, $ordinals));
Note the =>
instead of ,
in $func
.
The output is the same as before, and each can be partially applied in the same way as before.
Summary
The goal of the original question is to make the invocation of the call as simple as possible, at the expense of having a more complicated function that gets invoked; especially, to have the ability to pass the data array in as a single argument, without splitting the keys and values. Using the function supplied at the start of this answer:
$test_array = ["first_key" => "first_value",
"second_key" => "second_value"];
$array_map_assoc = function (callable $f, array $a) {
return array_column(array_map($f, array_keys($a), $a), 1, 0);
};
$f = function ($key, $value) {
return [$key, $key . ' loves ' . $value];
};
var_dump(array_values($array_map_assoc($f, $test_array)));
Or, for this question only, we can make a simplification to array_map_assoc()
function that drops output keys, since the question does not ask for them:
$test_array = ["first_key" => "first_value",
"second_key" => "second_value"];
$array_map_assoc = function (callable $f, array $a) {
return array_map($f, array_keys($a), $a);
};
$f = function ($key, $value) {
return $key . ' loves ' . $value;
};
var_dump($array_map_assoc($f, $test_array));
So the answer is NO, you can't avoid calling array_keys
, but you can abstract out the place where array_keys
gets called into a higher-order function, which might be good enough.
Solution 4:
$array = [
'category1' => 'first category',
'category2' => 'second category',
];
$new = array_map(function($key, $value) {
return "{$key} => {$value}";
}, array_keys($array), $array);