40

What is the fastest way to add string prefixes to array keys?

Input

$array = array(
 '1' => 'val1',
 '2' => 'val2',
);

Needed output:

$array = array(
  'prefix1' => 'val1',
  'prefix2' => 'val2',
);
hakre
  • 193,403
  • 52
  • 435
  • 836
Kirzilla
  • 16,368
  • 26
  • 84
  • 129

11 Answers11

73

Could do this in one long line I presume:

$prefix = "prefix";
$array = array_combine(
    array_map(
        function($k) use ($prefix) {return $prefix . $k; }, 
        array_keys($array)
     ),
    $array
);

Or -- as Sabrina points out in their answer -- using an arrow function syntax which is available from php7.4+.

There's probably dozens of ways to do this though:

$prefix = "prefix";
foreach ($array as $k => $v)
{
    $array[$prefix . $k] = $v;
    unset($array[$k]);
}

Now historically for versions of PHP prior to 5.3:


$prefix = "prefix";
$array = KeyPrefixer::prefix($array, $prefix);

class KeyPrefixer
{
    private $prefix;
    public function __construct($prefix) {
        $this->prefix = (string)$prefix;       
    }

    public static function prefix(array $array, $prefix)
    {
        $prefixer = new KeyPrefixer($prefix);
        return $prefixer->mapArray($array);
    }

    public function mapArray(array $array)
    {
        return array_combine(
            array_map(array($this, 'mapKey', array_keys($array)),
            $array
        );
    }

    public function mapKey($key) {
        return $this->prefix . (string)$key;
    }
}

which was originally without the $prefix as parameter and with a call to create_function(), which should not be used due to high in-own-foot-shooting-potential. Only for reference:

$array = array_combine(
    array_map(create_function('$k', 'return "prefix$k";'), array_keys($array)),
    $array
);

Direct compare with arrow function:

$array = array_combine(
    array_map(fn($k) => "prefix$k", array_keys($array)),
    $array
);

hakre
  • 193,403
  • 52
  • 435
  • 836
Lode
  • 1,075
  • 1
  • 8
  • 3
  • 2
    No need to set `array_values()` for `array_map()` as 2nd arg. If you just throw the array in it, it will use the value anyway. – kaiser Aug 06 '12 at 23:39
  • @hakre why does Sabrina's snippet need to be repeated? Why disempower her contribution? – mickmackusa May 07 '23 at 11:00
  • I am sorry Sabrina that @hakre has usurped the value of your recent/modern advice by building it into the top voted answer. Now, since your answer is unlikely to be read by researchers before Lode's, your answer will be utterly redundant and valueless. Thank you for necroposting modern syntax -- this is what helps SO pages to remain up-to-date. You have done nothing wrong. (There, I have apologized.) – mickmackusa May 07 '23 at 19:23
  • That's ironic. I feel you are overstepping. I've asked for a sanity check among peers that I trust. I'll see what they say. If Sabrina wanted her advice to be part of the top answer, she could have edited it herself. If I was Sabrina, I would be disappointed in the edit because it devalues my contribution. Necroposting is almost exclusively what I do now -- pulling my contribution into earlier answers would be doing me a disservice. I'm not sure how you don't see this. Did you ask Sabrina? There are very few new, unique PHP questions; posting modern advice on old pages is what is left. – mickmackusa May 07 '23 at 19:36
13

I've found that PHPBench is not a very good source for non-trivial benchmarks. So unless your actually interested in running for(....); it's not going to correctly show which syntax will be faster. I've put together a simple benchmark to show that foreach is actually the fastest when your use both the key and value during the iteration.

It's very important to actually force PHP to read the values from a loop iteration, or else it'll do its best to optimize them out. In the example below I use the doNothing function to force PHP to calculate the key and value each time. Using doNothing will cause an overhead to be applied to each loop, but it will be the same for each loop since the number of calls will be the same.

I wasn't really that surprised that foreach came out on top since it's the language construct for iterating a dictionary.

$array = range( 0, 1000000 );

function doNothing( $value, $key ) {;}

$t1_start = microtime(true);
foreach( $array as $key => $value ) {
    doNothing( $value, $key );
}
$t1_end = microtime(true);

$t2_start = microtime(true);
$array_size = count( $array );
for( $key = 0; $key < $array_size; $key++ ) {
    doNothing( $array[$key], $key );
}
$t2_end = microtime(true);

    //suggestion from PHPBench as the "fastest" way to iterate an array
$t3_start = microtime(true);
$key = array_keys($array);
$size = sizeOf($key);
for( $i=0; $i < $size; $i++ ) {
    doNothing( $key[$i], $array[$key[$i]] );
}
$t3_end = microtime(true);

$t4_start = microtime(true);
array_walk( $array, "doNothing" );
$t4_end = microtime(true);

print
    "Test 1 ".($t1_end - $t1_start)."\n". //Test 1 0.342370986938
    "Test 2 ".($t2_end - $t2_start)."\n". //Test 2 0.369848966599
    "Test 3 ".($t3_end - $t3_start)."\n". //Test 3 0.78616809845
    "Test 4 ".($t4_end - $t4_start)."\n"; //Test 4 0.542922019958

Edit: I'm using PHP 5.3 on 64-bit Mac OSX 10.6

Kendall Hopkins
  • 43,213
  • 17
  • 66
  • 89
  • It's a bit weird because I'm getting values 10 times lower: `0.043029069900513`. Do you know why that might be happening? It's PHP 5.4.3. And if I run this for just a single `foreach` and not my a bit longer script then it's: `6.4849853515625E-5` – Atadj Jun 30 '13 at 11:22
  • They seem to be in the right ballpark for me: http://codepad.viper-7.com/b1gHRC. Foreach is still the clear winner. – Kendall Hopkins Jun 30 '13 at 17:59
  • Yes, foreach is the winner but I wonder why this website shows `0.0497624` and my website, with exactly the same script shows `0.0000871`. – Atadj Jun 30 '13 at 18:35
  • Sounds like you had a typo that caused it not to run correctly. – Kendall Hopkins Jun 30 '13 at 18:44
  • Or maybe you have a different hosting with different specifications..? – Smuuf Aug 10 '13 at 16:58
  • I find this to be an inappropriate post for this page (I'll go as far as to say that it is Not An Answer) because it makes no attempt to benchmark actual techniques that will prepend a string to the keys of an array. This is probably the correct answer to a different question such as: [Performance of FOR vs FOREACH in PHP](https://stackoverflow.com/q/3430194/2943403) or [Is using native PHP functionality faster than doing the same thing in PHP loops? Why?](https://stackoverflow.com/q/25672638/2943403) or any one of the other 30-odd pages on iteration in PHP. – mickmackusa May 07 '23 at 20:37
7
function keyprefix($keyprefix, Array $array) {

    foreach($array as $k=>$v){
        $array[$keyprefix.$k] = $v;
        unset($array[$k]);
    }

    return $array; 
}

Using array_flip will not preserve empty or null values. Additional code could be added in the unlikely event that the prefixed key already exists.

mistajolly
  • 441
  • 4
  • 5
2

If you don't want to use for loop you can do:

// function called by array_walk to change the $value  in $key=>$value.
function myfunction(&$value,$key) {
    $value="prefix$value";
}

$keys = array_keys($array);  // extract just the keys.
array_walk($keys,"myfunction"); // modify each key by adding a prefix.
$a = array_combine($keys,array_values($array)); // combine new keys with old values.

I don't think this will be more efficient than the for loop. I guess array_walk will internally use a loop and there is also the function call overhead here.

codaddict
  • 445,704
  • 82
  • 492
  • 529
  • 1
    while working, that's incredibly slow compared to a for loop isn't it? – Gordon Apr 09 '10 at 13:26
  • 1
    You should not use by-reference. This is actually slower and more intensive than just passing and return it. Cant find docs real quick atm. – Martijn Jul 10 '14 at 10:17
2

Another way to write @Lode's first snippet using arrow function syntax which is available from php7.4+:

function prefixArrayKeys($array, $prefix) {
    return array_combine(
        array_map(fn($k) => $prefix . $k, array_keys($array)),
        $array
    );
}
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Sabrina
  • 2,531
  • 1
  • 32
  • 30
1
function array_key_prefix_suffix(&$array,$prefix='',$suffix=''){
        $key_array = array_keys($array);
        $key_string = $prefix.implode($suffix.','.$prefix,$key_array).$suffix;
        $key_array = explode(',', $key_string);
        $array = array_combine($key_array, $array);
    }

This is implemented and working very well

0

Another way to do achieve is with array_flip()

<?php 
    $data = array_flip($data);
    foreach($data as $key => &$val) { $val = "prefix" . $val; }
    $data = array_flip($data);
adamS
  • 592
  • 8
  • 14
0

I would create a completely new array, and create your new keys. That has to be faster than unsetting all unwanted keys;

$prefixed_array = array();

foreach ($array as $key => $value) {
    $prefixed_array[ $prefix . $key] = $value;
}

And if you want to do any other "affix"'s

function array_affix_keys($affix, Array $array, $type = 'prefix', $options = array()){

    $affixed_array = array();

    if($type =='prefix'){
        foreach ($array as $key => $value) {$affixed_array[ $affix . $key] = $value;}
        return $affixed_array;
    }
    if($type =='suffix'){
        foreach ($array as $key => $value) {$affixed_array[$key . $affix ] = $value;}
        return $affixed_array;
    }
    if($type =='circumfix'){

        if(is_array($affix) && count($affix) == 2){

            foreach ($array as $key => $value) {
                $affixed_array[ $affix[0] . $key . $affix[1] ] = $value;
            }
        }
        return $affixed_array;
    }
    if($type == 'simulfix' && isset($options['phonemes'])){
        foreach ($array as $key => $value) { $affixed_array[ str_replace($options['phonemes'], $affix, $key) ] = $value;}
        return $affixed_array;
    }
    return $array;
}


$prefixed = array_affix_keys('prefix_', $array);
$prefixed = array_affix_keys('prefix_', $array, 'prefix');

$suffixed = array_affix_keys('_suffix', $array, 'suffix');
$circumfixed = array_affix_keys(array('prefix', 'suffix'), $array, 'circumfix');
$simulfix = array_affix_keys('replace', $array, 'simulfix', array('phonemes' => 'find'));
TarranJones
  • 4,084
  • 2
  • 38
  • 55
  • This implementation of `circumfix` is wrong. Circumfix has different ending than the start, e.g. `per(...)kan`. [Wiki](https://en.wikipedia.org/wiki/Circumfix) – nass Mar 02 '17 at 12:34
0

Here's a fast, one-liner solution (supported on PHP 4+) to add a prefix and/or suffix using implode / explode:

$array = range(0, 1000000);
$delimiter = '-';
$prefix = 'string';
$suffix = '';

$result = array_combine(explode($delimiter, $prefix . implode($suffix . $delimiter . $prefix, array_keys($array)) . $suffix), $array);
0

What isn't surprising is that a classic foreach() loop is the best performer. It also happens to be the most concise script. If your code styling preference doesn't demand functional syntax, then this is probably the best approach. That said, if your input volume is significantly large, it will be a good idea to benchmark your actual data if performance is a high priority.

What is surprising (to me anyway), is that calling three specific native functions (array_combine(), array_map(), array_keys()) can outperform two specific native functions (array_reduce(), array_keys()) and even one native function (array_walk())!

array_walk() isn't horrifically slow, but array_reduce() suffers wicked performance drag.

The following benchmarks are the average of 20 calls of the technique upon arrays with 5000 elements. (Benchmark Script)

  1. foreach(): (PHP8.2.5 exec time: 0.36159753799438)

    $result = [];
    foreach ($array as $k => $v) {
        $result[$prefix . $k] = $v;
    }
    
  2. array_combine(),array_map(),array_keys(): (PHP8.2.5 exec time: 0.52124261856079)

    $result = array_combine(
        array_map(
            fn($k) => $prefix . $k,
            array_keys($array)
        ),
        $array
    );
    
  3. array_walk(): (PHP8.2.5 exec time: 0.77606439590454)

    $result = [];
    array_walk(
        $array,
        function($v, $k, $prefix) use (&$result) {
            $result[$prefix . $k] = $v;
        },
        $prefix
    );
    
  4. array_reduce(),array_keys(): (PHP8.2.5 exec time: 49.715828895569)

    $result = array_reduce(
        array_keys($array),
        function($res, $k) use ($array, $prefix) {
            $res[$prefix . $k] = $array[$k];
            return $res;
        },
        []
    );
    

    Using arrow function syntax with array_reduce() does not appear to improve performance.

    $result = array_reduce(
        array_keys($array),
        fn($result, $k) => $result + [$prefix . $k => $array[$k]],
        []
    );
    
  5. generator: (PHP8.2.5 exec time: 0.58324337005615)

    function generator($array, $prefix) {
        foreach ($array as $k => $v) {
            yield $prefix . $k => $v;
        }
    }
    $result = iterator_to_array(generator($array, $prefix));
    
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
-1

I figured out one-line solution:

array_walk($array, create_function('$value, &$key', '$key = "prefix" . $key;'));
fracz
  • 20,536
  • 18
  • 103
  • 149
  • 1
    This doesn't work. The keys are not changed. It's the same as I've been trying to do for the better part of an hour: http://codepad.org/h88Is8ug – Travesty3 Jan 04 '13 at 20:55