Is there a shorthand for the following code:
$result = array_combine(
array_map(fn($elem) => "key_$elem", $array),
array_map(fn($elem) => "value_$elem", $array)
);
I do not like the idea (from readability point of view) of having to use array map twice on the same array and then combining the intermediate results.
To my surprise, substr_replace()
calls inside of array_combine()
(solitary demo) outperforms foreach()
frequently enough to consider it "the best" in my eyes. (Demo)
function substrReplaceCombine($array) {
return array_combine(
substr_replace($array, 'key', 0, 0),
substr_replace($array, 'value', 0, 0)
);
}
Output for 8.3.4 (hand sorted ASC)
Duration of substrReplaceCombine: 0.71814060211182
Duration of reduce: 0.93705654144287
Duration of construct: 0.95775127410889
Duration of mapCombine: 1.0146498680115
Duration of mapFlatten: 1.1467099189758
Duration of mapUncolumn: 1.1523962020874
Duration of walk: 1.2235164642334
Duration of generator: 1.5499591827393
Original answer:
I find this contrived task to be rather unrealistic (unfathomable as a real word use case). The preference of code styling is going to come down to personal preference.
If we are going to consider options from an academic vector, then we should not only compare code brevity, but also time complexity, directness, minimizing function calls, and perhaps even memory.
TL;DR:
Classic language construct iteration (like foreach()
) will consistently outperform functional iterators. Using generators means a trade off of performance for memory savings.
All other functional iterators are near enough in there execution time that you probably shouldn't waste development time thinking about which one is fastest.
Of all of the approaches that I tested, only array_reduce()
encountered a catastrophic memory problem. After researching, I found that using an ArrayObject will overcome the under-the-hood memory challenges (for when you cannot simply increase the memory allowance).
Now I will list the different approaches pulled from this earlier answer to a VERY similar question which I tweaked and extended with a few more approaches.
Generator with foreach()
function generate($array) { // 1 loop over data
foreach ($array as $v) {
yield 'key' . $v => 'value' . $v;
}
}
function generator($array) { // 1 parent loop over data
return iterator_to_array(generate($array));
}
An ordinary foreach()
loop:
function construct($array) { // 1 loop over data
$result = [];
foreach ($array as $v) {
$result['key' . $v] = 'value' . $v;
}
return $result;
}
array_combine()
with two nested array_map()
calls:
function mapCombine($array) { // 3 loops over data
return array_combine(
array_map(
fn($v) => 'key' . $v,
$array
),
array_map(
fn($v) => 'value' . $v,
$array
),
);
}
Flattening an 2d array:
function mapFlatten($array) { // 3 loops over data
return array_merge(
...array_map(
fn($v) => ['key' . $v => 'value' . $v],
$array
),
);
}
Convert a 2d array into an associative with array_column()
:
function mapUncolumn($array) { // 2 loops over data
return array_column(
array_map(
fn($v) => ['key' . $v, 'value' . $v],
$array
),
1,
0
);
}
array_walk()
with reference array:
function walk($array) { // 1 loop over data
$result = [];
array_walk(
$array,
function($v) use (&$result) {
$result['key' . $v] = 'value' . $v;
}
);
return $result;
}
array_reduce()
built to handle large payloads:
function reduce($array) { // 1 loop over data
return array_reduce(
$array,
function($result, $v) {
$result['key' . $v] = 'value' . $v;
return $result;
},
new ArrayObject()
);
}
My testing script: (Demo)
function returnTime(callable $function, int $repeat = 20)
{
$tests = [];
for ($i = 0; $i < $repeat; ++$i) {
$startTime = microtime(true);
$function();
$endTime = microtime(true);
$tests[] = $endTime - $startTime;
}
// Representing the average
return 1000 * array_sum($tests) / $repeat;
}
$array = range(0, 5000);
foreach (['generator', 'construct', 'mapCombine', 'mapFlatten', 'mapUncolumn', 'walk', 'reduce'] as $test) {
echo "Duration of $test: ", returnTime(fn() => $test($array)) . PHP_EOL;
}
Results for PHP8.3.4 (sorted by speed):
Duration of construct: 0.72321891784668
Duration of reduce: 0.91509819030762
Duration of mapCombine: 0.94027519226074
Duration of mapFlatten: 1.1523604393005
Duration of walk: 1.1601090431213
Duration of mapUncolumn: 1.163923740387
Duration of generator: 1.2189626693726
Results for PHP8.3.3 (sorted by speed):
Duration of construct: 0.68153142929077
Duration of reduce: 0.91077089309692
Duration of generator: 0.93502998352051
Duration of mapCombine: 0.94808340072632
Duration of mapFlatten: 1.1013269424438
Duration of walk: 1.132333278656
Duration of mapUncolumn: 1.1387705802917
If I was to need such a process to be executed in a professional application, I'd probably use @User863's snippet -- it is clean, direct, functional-style, and doesn't bother making more than one iteration.
Note that I was not able to use the union operator +
in my array_reduce()
script because it doesn't work with ArrayObject type data.
If I had performance concerns, I'd use a foreach.
If I had memory concerns, I'd use a generator.