1

I am doing this to echo the minimum value in an array...

$array = [
[
    'a' => 0,
    'f' => 0,
    'f' => 0,
    'l' => 61.60
],
[
    'a' => 38,
    'f' => 0,
    'f' => 0,
    'l' => 11.99
],
[
    'a' => 28,
    'f' => 0,
    'f' => 0,
    'l' => 3.40
 ]
];

$min = min(array_column($array, 'a'));

echo $min;

Now I want to exclude 0 from the results, I know I can use array_filter to achieve this but do i need to process the array twice?

RAUSHAN KUMAR
  • 5,846
  • 4
  • 34
  • 70
fightstarr20
  • 11,682
  • 40
  • 154
  • 278
  • 2
    It will loop over the array three times in total if you throw in an `array_filter`, yes. Benchmark whether that is any real issue in practice if you're concerned. – deceze Jul 28 '17 at 08:08
  • It is not an issue with this sample dataset but could become one when live. Will take your advice and benchmark – fightstarr20 Jul 28 '17 at 08:10
  • 2
    You can use [`array_reduce()`](http://php.net/manual/en/function.array-reduce.php) to walk the array only once and implement the value retrieval, the filtering and the computation of the minimum in the callback function. – axiac Jul 28 '17 at 08:11
  • 1
    @axiac Yes, that would be my goto replacement as well, but whether that's actually more efficient or not still needs to be benchmarked. `min`, `array_column` and `array_filter` are all implemented in C, vs a PHP callback function, so it might be a tossup after all. – deceze Jul 28 '17 at 08:12
  • 1
    Could you somehow use [yield](https://stackoverflow.com/questions/17483806/what-does-yield-mean-in-php) to tackle this better? – Sina Jul 28 '17 at 08:17
  • Your problem is undefined if the array contains only `0` in column `a`. – axiac Jul 28 '17 at 08:20
  • FWIW: `min(v['a'] for v in arr if v['a'] != 0)`… one iteration over a generator. Yay Python… ;-) – deceze Jul 28 '17 at 08:38

5 Answers5

5

Yes, this will do:

$min = min(array_filter(array_column($array, 'a')));

It will iterate the array three times, once for each function.

You can use array_reduce to do it in one iteration:

$min = array_reduce($array, function ($min, $val) {
    return $min === null || ($val['a'] && $val['a'] < $min) ? $val['a'] : $min;
});

Whether that's faster or not must be benchmarked, a PHP callback function may after all be slower than three functions in C.

A somewhat more efficient solution without the overhead of a function call would be a good ol' loop:

$min = null;
foreach ($array as $val) {
    if ($min === null || ($val['a'] && $val['a'] < $min)) {
        $min = $val['a'];
    }
}

In the end you need to benchmark and decide on the correct tradeoff of performance vs. readability. In practice, unless you have positively humongous datasets, the first one-liner will probably do just fine.

deceze
  • 510,633
  • 85
  • 743
  • 889
3

A solution using array_reduce() to walk the array only once.

$min = array_reduce(
    $array,
    function($acc, array $item) {
        return min($acc, $item['a'] ?: INF);
    },
    INF
);

How it works:

It starts with +INF as the partial minimum value. All the values it encounters in array are, theoretically smaller than that.

The callback function ignores the items having 0 (or another value that is equal to FALSE when evaluated as boolean). The expression $item['a'] ?: INF uses INF (infinity) instead of $item['a'] to avoid altering the partial result (to ignore 0 values).

It returns the minimum between the current partial minimum (passed by array_reduce() in parameter $acc) and the value of the current item, as explained above.

The value in $min is the minimum of the not FALSE-ey values in column a of the items in $array. If all these values are 0 (FALSE), the value returned in $min is INF.

axiac
  • 68,258
  • 9
  • 99
  • 134
  • 1
    I was thinking about this approach, but opted to have `null` as the default return value instead of `INF`. Makes more sense IMO, but depends on OP's goals. – deceze Jul 28 '17 at 08:29
  • I also tried with `NULL` but `min()` compares the values using the usual comparison rules. According to them, `min(NULL, 28) == NULL` and this renders the entire loop useless. – axiac Jul 28 '17 at 09:03
  • Yeah, I know, that's why my version is so much more cumbersome than yours. – deceze Jul 28 '17 at 09:08
3

This is not an answer but the format of its content cannot be provided by a comment. It also cannot stay in my answer as it is technically not part of it.

I generated a benchmark for the three solutions provided by @deceze and my solution and ran it using PHP 7.0. Everything below applies only to PHP 7.x.

PHP 5 runs much slower and it requires more memory.


I started by running the code 1,000,000 times over a small list of 100 items then I iteratively divided the number of iteration by 10 while multiplied the list length by 10.

Here are the results:

$ php bench.php 100 1000000
Generating 100 elements... Done. Time: 0.000112 seconds.
array_filter(): 3.265538 seconds/1000000 iterations. 0.000003 seconds/iteration.
foreach       : 3.771463 seconds/1000000 iterations. 0.000004 seconds/iteration.
reduce @deceze: 6.869162 seconds/1000000 iterations. 0.000007 seconds/iteration.
reduce @axiac : 8.599051 seconds/1000000 iterations. 0.000009 seconds/iteration.

$ php bench.php 1000 100000
Generating 1000 elements... Done. Time: 0.000750 seconds.
array_filter(): 3.024423 seconds/100000 iterations. 0.000030 seconds/iteration.
foreach       : 3.997505 seconds/100000 iterations. 0.000040 seconds/iteration.
reduce @deceze: 6.669426 seconds/100000 iterations. 0.000067 seconds/iteration.
reduce @axiac : 8.342756 seconds/100000 iterations. 0.000083 seconds/iteration.

$ php bench.php 10000 10000
Generating 10000 elements... Done. Time: 0.002643 seconds.
array_filter(): 2.913948 seconds/10000 iterations. 0.000291 seconds/iteration.
foreach       : 4.190049 seconds/10000 iterations. 0.000419 seconds/iteration.
reduce @deceze: 9.649768 seconds/10000 iterations. 0.000965 seconds/iteration.
reduce @axiac : 11.236113 seconds/10000 iterations. 0.001124 seconds/iteration.

$ php bench.php 100000 1000
Generating 100000 elements... Done. Time: 0.042237 seconds.
array_filter(): 90.369577 seconds/1000 iterations. 0.090370 seconds/iteration.
foreach       : 15.487466 seconds/1000 iterations. 0.015487 seconds/iteration.
reduce @deceze: 19.896064 seconds/1000 iterations. 0.019896 seconds/iteration.
reduce @axiac : 15.056250 seconds/1000 iterations. 0.015056 seconds/iteration.

For lists up to about 10,000 elements, the results are consistent and they match the expectations: array_filter() is the fastest, foreach comes close then the array_reduce() solutions aligned by the number of functions they call (@deceze's is faster as it doesn't call any function, mine's calls min() once). Even the total running time feels consistent.

The value of 90 seconds for the array_filter() solution for 100,000 items in the list looks out of place but it has a simple explanation: both array_filter() and array_column() generate new arrays. They allocate memory and copy the data and this takes time. Add the time needed by the garbage collector to free all the small memory blocks used by a list of 10,000 small arrays and the running time will go up faster.

Another interesting result for 100,000 items array is that my solution using array_reduce() is as fast as the foreach solution and better than @deceze's solution using array_reduce(). I don't have an explanation for this result.

I tried to find out some thresholds when these things start to happen. For this I ran the benchmark with different list sizes, starting from 5,000 and increasing the size by 1,000 while keeping the total number of visited items to 100,000,000. The results can be found here.

The results are surprising. For some sizes of the list (8,000, 11,000, 12,000, 13,000, 17,000 items), the array_filter() solution needs about 10 times more time to complete than any solution that uses array_reduce(). For other list sizes, however, it goes back to the track and completes the 100 million node visits in about 3 seconds while the time needed by the other solutions constantly increases as the list length increases.

I suspect the culprit for the hops in the time needed by the array_filter() solution is the PHP's memory allocation strategy. For some lengths of the initial array, the temporary arrays returned by array_column() and array_filter() probably trigger more memory allocation and garbage cleanup cycles than for other sizes. Of course, it is possible that the same behaviour happens on other sizes I didn't test.

Somewhere around 16,000...17,000 items in the list, my solution starts running faster than @deceze's solution using array_reduce() and around 25.000 it starts performing equally fast as the foreach solution (and even faster sometimes).

Also for lists longer than 16,000-17,000 items the array_filter() solution consistently needs more time to complete than the others.


The benchmark code can be found here. Unfortunately it cannot be executed on 3v4l.org for lists larger than 15,000 elements because it reaches the memory limit imposed by the system.

Its results for lists larger than 5,000 items can be found here.

The code was executed using PHP 7.0.20 CLI on Linux Mint 18.1. No APC or other kind of cache was involved.

Conclusion

For small lists, up to 5,000 items, use the array_filter(array_column()) solution as it performs well for this size of the list and it looks neat.

For lists larger than 5,000 items switch to the foreach solution. It doesn't look well but it runs fast and it doesn't need extra memory. Stick to it as the list size increases.

For hackatons, interviews and to look smart to your colleagues, use any array_reduce() solution. It shows your knowledge about PHP array functions and your understanding of the "callback" programming concept.

Community
  • 1
  • 1
axiac
  • 68,258
  • 9
  • 99
  • 134
1

Try with array_flip and unset() php function
like this

  $array = [
[
    'a' => 0,
    'f' => 0,
    'f' => 0,
    'l' => 61.60
],
[
    'a' => 38,
    'f' => 0,
    'f' => 0,
    'l' => 11.99
],
[
    'a' => 28,
    'f' => 0,
    'f' => 0,
    'l' => 3.40
 ]
];
$min = array_flip(array_column($array, 'a'));
unset($min[0]);
$min=min(array_flip($min));

o/p

   28    
Bibhudatta Sahoo
  • 4,808
  • 2
  • 27
  • 51
-2

You can use sort:

$array = [
[
'a' => 0,
'f' => 0,
'f' => 0,
'l' => 61.60
],
[
'a' => 38,
'f' => 0,
'f' => 0,
'l' => 11.99
],
[
'a' => 28,
'f' => 0,
'f' => 0,
'l' => 3.40
 ]
];

$array = array_column($array, 'a');

sort($array);

echo $array[1];
Mahdi
  • 664
  • 3
  • 15
  • 35