phparraysassociative-arrayarray-maparray-combine

Use flat array's values to generate an associative array whose keys and values are given a static prefix


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

PHP Sandbox

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.


Solution

  • Update 2024-03-08:

    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:

    1. Classic language construct iteration (like foreach()) will consistently outperform functional iterators. Using generators means a trade off of performance for memory savings.

    2. 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.

    3. 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.

    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.