Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge same array multiple times?

Tags:

arrays

php

I would like to ask for help with this simple task. Let's say I have an array('a', 'b', 'c'). What I want is to merge exactly the same array multiple times into same or new empty array. For example, merging it 3x would produce this:

array('a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c')

I know, there is array_merge, but how to use it if I have optional number of times the array should be merged? Of course I could loop and merge:

$new = array();
for ($i=0; $i < $howManyTimes; $i++) {
    $new = array_merge($new, array('a', 'b', 'c'));
}

Or I could use hack, juggling between strings and arrays:

$new = str_split(str_repeat(implode('', array('a', 'b', 'c')), $howManyTimes));

However, none of those approaches feels good, so I would appreciate any ideas and other/ better ways doing what I need in PHP(5).

Thanks!

EDIT: After getting the answer from @Amal Murali, it seems there is not really any better or more elegant solution. However, to make this question a little more valuable I did some tests. With 10 000 iterations and using the example array:

  • array_merge() in loop took about 37 secs
  • array to string to array trick took laughable 7 ms!!

It is not that unexpected as array_merge probably does array scan on every iteration, but still good thing to remember.

Anyway, thank you all for your inputs;)

like image 379
lp1051 Avatar asked Jun 17 '14 17:06

lp1051


2 Answers

Speed vs. "Elegant"

The thing is: array_merge() is just slow in PHP. Why? Well, that's because it does exactly it's name thing: it merges hashtables. In PHP, arrays are hashtables and merge operation on such structure can not be fast by definition.

However, you may want to have some "elegant" way to resolve a matter. That's why first thing that comes in mind is array_merge(). But - this is the case when plain loop will be faster. So you need to decide what is your intention - to have "nice" code or to have "fast" code.

Dealing with array_merge()

Actually, array_merge() is not good for this case. That is because it will call this function exactly N times, where N is repetition count. Pretty bad, since, as I've already said, array_merge() is slow (because of hashtable operations cost).

Still, you may do this with proper usage of this function:

function repeatMerge($array, $rCount)
{
    return call_user_func_array('array_merge', array_fill(1, $rCount, $array));
}

Why this is better than option with array_walk()? Because function above will call array_merge() only once: it will gather all needed arrays firstly, then - call merging function on them at once. Thus, all hashtables operations overhead will be done only once. Thus, option above will have execution time near plain loop (which is the fastest for this issue)

Plain loop & speed

The fastest way is just to avoid array_merge() at all. Like:

function repeatLoops($array, $rCount)
{
    $result = [];
    $eCount = count($array);
    for($j=0; $j<$rCount; $j++)
    {
        for($i=0; $i<$eCount; $i++)
        {
           $result[]=$array[$i];
        }
    }
    return $result;
}

Yes. We're using two nested loops. But we're always writing to array's end, it's fast operation. But, in general, we'll win only overhead that array_merge() has - since at low-level both options will boil down to writing into hashtable.

So, test that

I've used Benchmark for relative tests of functions above. You may see that, in fact, second option won only "some" time (so total time is lesser, but only by some constant) - which is expected - as I've stated, both functions will do same work, only difference is that first function will have array_merge() overhead, which will be done once. Here are tesing data:

$array      = range(1, 1000);
$count      = 1000;

And testing results for 1E1 times:

//first function (repeatMerge), 1E1=10 times
array(3) {
  [1]=>
  float(2.1156709194183)
  [2]=>
  float(0.21156709194183)
  [3]=>
  int(10)
}
//second function (repeatLoops), 1E1=10 times
array(3) {
  [1]=>
  float(1.6837940216064)
  [2]=>
  float(0.16837940216064)
  [3]=>
  int(10)
}

and for 1E2 times:

//first function (repeatMerge), 1E2=100 times
array(3) {
  [1]=>
  float(20.907063007355)
  [2]=>
  float(0.20907063007355)
  [3]=>
  int(100)
}
//second function (repeatLoops), 1E2=100 times
array(3) {
  [1]=>
  float(16.947901964188)
  [2]=>
  float(0.16947901964188)
  [3]=>
  int(100)
}

(you can check paste for the code here - it's impossible to make fiddle since it uses third-party library and, besides, fiddles are limited for execution time). Little explanation (if you won't check that on github).

  • [1] (first index) in array: total execution time
  • [2] (second index) in array: avg. execution time
  • [3] (third index) in array: total iterations count
like image 74
Alma Do Avatar answered Oct 12 '22 19:10

Alma Do


The accepted answer is absolutely correct. But I'd like to present a little addition to it.

This is an alternative solution to @alma-do's repeatMerge(…) function:

$new = array_merge(...array_fill(0, $howManyTimes, ['a', 'b', 'c']));

It's basically the same approach. Probably there won't be even much difference to the benchmark outcome in comparison to repeatMerge(…). It just has a different notation. Although this one feels somehow elegant at least for me, this solution may be not very familiar to others because it's slightly harder to read due to the splat operator as introduced to PHP 5.6 years ago.

like image 33
Arvid Avatar answered Oct 12 '22 21:10

Arvid