5

Suppose you need to create a 'top' of some sort and have code like this:

$matches=array();
foreach ($array as $v){
   $matches[processing($v)]++;  
}

This will output a Notice: Undefined index for the cases the index needs creating.

What would be the best way to tackle these cases since you KNOW you'll have to create indexes?

I used these solutions depending on case:

  1. Suppressing the error @$matches[$v]++;
    Pro: VERY easy to type
    Con: slow
  2. Checking if it's set $matches[$v]=isset($matches[$v])?$matches[$v]++:1;
    Pro: faster
    Con: takes longer to write even in the shorthand form and need to use $matches[$v] 2 more times

Are there any other ways?
Looking for the fastest execution time as I'm using this function thousands of times or some lazier way to type that's still faster than @

EDIT:

In a simple case where you have $matches[$v]++; you could also use array_count_values() (as Yoshi suggested)

CSᵠ
  • 10,049
  • 9
  • 41
  • 64
  • 1
    try [array_count_values](http://php.net/manual/function.array-count-values.php): `$matches = array_count_values($array);` – Yoshi Jan 10 '13 at 15:07
  • I don't see how (2) can be faster than (1), given that (2) performs the same calls as (1) and then some (a conditional and `isset`). – Frerich Raabe Jan 10 '13 at 15:09
  • @Yoshi: good one, thank you, updated my question. I'm doing some processing inside the loop and can't count that simple. – CSᵠ Jan 10 '13 at 15:28
  • @FrerichRaabe: you'd think that but it appears `isset()` is faster: [benchmark](http://stackoverflow.com/a/7200025/731947) – CSᵠ Jan 10 '13 at 15:32
  • @TheOptometrist if `processing($v)` allways returns the same value for the same `$v`, then you could still use `array_count_values` and apply `processing()` on the keys of the resulting array. ;) – Yoshi Jan 10 '13 at 15:40
  • @TheOptometrist I'm a bit curious, how would you use the multiple values returned from `processing()` as array key? php would try to convert those values to a string before using them as a key, which could possible cause problems!? – Yoshi Jan 11 '13 at 07:54
  • Also, regarding your comment to Marc's answer. I wouldn't know of any value you could possible return from a function, which you can't assign to a variable!? – Yoshi Jan 11 '13 at 08:05
  • @Yoshi: indeed you could assign the value of a function to any var, I gave the 'processing()' as an example when actually I have a while inside it that increments the array key based on some if's so it's not actually `$matches[processing($v)]++`, just wrote it like that for simplicity and to emphasize on the increment part. actually it doesn't matter what the function does as this question is aimed at a fast increment of *possible* non-indexed arrays for very large datasets – CSᵠ Jan 12 '13 at 06:37

4 Answers4

7

After some reading, writing and testing I got something:

function inc(&$var){
    if (isset($var)) $var++;else $var=1;
}

and thought I struck gold, but let's see the tests first...

Test code:

$a=array();

// Pre-Fill array code goes here
for($i=1;$i<100000;$i++) {
    $r=rand(1,30000);
    //increment code goes here
}

// Remove extra keys from array with:
//foreach ($a as $k=>$v) if ($v==0) unset($a[$k]);

Execution times: (for informative purposes only)

inc($a[$r])                             1.15-1.24
@$a[$r]++                                   1.03-1.09
$a[$r]=array_key_exists($r,$a)?$a[$r]++:1;  0.99-1.04

$a[$r]=!empty($a[$r])?$a[$r]++:1;               0.61-0.74
if (!empty($a[$r])) $a[$r]++;else $a[$r]=1; 0.59-0.67
$a[$r]=isset($a[$r])?$a[$r]++:1;                0.57-0.65
if (isset($a[$r])) $a[$r]++;else $a[$r]=1;  0.56-0.64


//with pre-fill
$a=array_fill(0,30000,0);                   +0.07(avg)
for($i=1;$i<=30000;$a[$i++]=0);             -0.04(avg)

//with pre-fill and unset
$a=array_fill(0,45000,0);                   +0.16(avg)
for($i=1;$i<=45000;$a[$i++]=0);             +0.02(avg)

Conclusions:

  • @ is of course the fastest to type and I don't see any problem in using it in this case but feel free to check this question also: Suppress error with @ operator in PHP
  • completely suppressing errors (before the loop and enabling errors after the loop) via ini_set() is worse than all on performance
  • inc() looks nice and clean, easy to type and does checking instead of suppressing, but calling it looks to be even slower than @
  • isset() is slightly faster than empty(), but both perform fairly the same
  • interestingly using shorthand if statements is slightly slower!
  • best results achieved when pre-filling the array. Even if length is unknown a good prediction would be still slightly faster on a huge dataset
  • strangely, array_fill() takes slightly longer than for ?!?!

RFC

I don't consider this answer 100% complete, although, for now it looks like isset() is the fastest and @ the laziest.
Any comments and ideas are appreciated!

Community
  • 1
  • 1
CSᵠ
  • 10,049
  • 9
  • 41
  • 64
  • Thanks for your detailed response. I used inc(); – John Ballinger Jan 14 '15 at 21:18
  • Your shorthand `if` might have been slower because of an extra assignment - you do a redundant `$a[$r]=$a[$r]++`. I suppose it should be more equivalent to the normal `if` if you dropped the left side and did just `isset($a[$r]) ? $a[$r]++ : $a[$r]=1;` – Džuris Aug 18 '17 at 02:15
1

You could perhaps initialise the matches array to begin with, using array_combine to combine the values from $array as keys and array_fill to fill the values with an initial 0

$matches = array_combine(array_values($array), array_fill(0, count($array), 0));
Crisp
  • 11,417
  • 3
  • 38
  • 41
0

I always do:

$matches=array();

foreach ($matches as $v){

    /* if $v is not empty, add 1, otherwise $v= 1 */
    $matches[$v]=(!(empty($matches[$v]))) ? $matches[$v]++ : 1;
}

You're right. It's a little wordy but to be fair it's quite succinct at the same time. I use empty() instead of isset() though. Not sure if that's faster or slower off the top of my head. I think it's probably slower.

Edit

To answer your edit, I'd do it like this:

$matches=array();

foreach ($matches as $v){

    $x=function($v);

    /* if $v is not empty, add 1, otherwise $v= 1 */
    $matches[$x]=(!(empty($matches[$x]))) ? $matches[$x]++ : 1;
}

That way, you only call the function once.

Marc
  • 139
  • 8
  • `!empty()` is pretty much like `isset()` to write, would worth it only if it would be faster – CSᵠ Jan 10 '13 at 15:37
0

PHP 7+ solution:

$arr[$key] = ($arr[$key] ?? 0) + 1;
rybo111
  • 12,240
  • 4
  • 61
  • 70