PHP array_column 和 array_filter

PHP array_column with array_filter

我这样做是为了回显数组中的最小值...

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

现在我想从结果中排除 0,我知道我可以使用 array_filter 来实现这个但我需要处理数组两次吗?

您可以使用 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];

尝试使用 array_flipunset() php 函数
像这样

  $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    

是的,这样做:

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

它将迭代数组三次,每个函数一次。

您可以使用 array_reduce 一次迭代完成:

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

是否必须进行基准测试,PHP 回调函数毕竟可能比 C 中的三个函数慢。

没有函数调用开销的更有效的解决方案将是一个很好的循环:

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

最后,您需要进行基准测试并决定性能与可读性之间的正确权衡。在实践中,除非你有非常庞大的数据集,否则第一个单行可能就可以了。

使用 array_reduce() 仅遍历数组一次的解决方案。

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

工作原理:

+INF开头为部分最小值。它在数组中遇到的所有值,理论上都小于它。

回调函数忽略具有 0(或等于 FALSE when evaluated as boolean). The expression $item['a'] ?: INF uses INF(无穷大)而不是 $item['a'] 的其他值以避免改变部分结果(忽略 0 值)。

它returns当前部分最小值(由参数$acc中的array_reduce()传递)和当前项目的值之间的最小值,如上所述。

$min 中的值是 $array 中项目的 a 列中非 FALSE-ey 值的最小值。如果所有这些值都是 0 (FALSE),则 $min 中返回的值为 INF.

这不是答案,但评论无法提供其内容的格式。它也不能留在 中,因为它在技术上不是它的一部分。

我使用 PHP 7.0 为 provided by @deceze and 和 运行 生成了一个基准。以下所有内容仅适用于 PHP 7.x.

PHP 5 运行速度慢得多,需要更多内存。


我开始 运行 代码 1,000,000 遍历 100 项的小列表然后我迭代地将迭代次数除以 10 同时乘以列表长度 10.

结果如下:

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

对于最多约 10,000 个元素的列表,结果是一致的并且符合预期:array_filter() 是最快的,foreach 接近于 array_reduce()根据调用的函数数量对齐的解决方案(@deceze 的速度更快,因为它不调用任何函数,我的调用 min() 一次)。即使总 运行 时间感觉一致。

列表中 100,000 项的 array_filter() 解决方案的 90 秒值看起来不合适,但它有一个简单的解释:array_filter()array_column() 生成新数组。他们分配内存并复制数据,这需要时间。加上垃圾收集器释放 10,000 个小数组列表使用的所有小内存块所需的时间,运行 时间会增加得更快。

100,000 items 数组的另一个有趣结果是 使用 array_reduce()foreach 解决方案一样快,并且优于 @deceze 使用 [=18] 的解决方案=].我没有对这个结果的解释。

当这些事情开始发生时,我试图找出一些阈值。为此,我 运行 具有不同列表大小的基准,从 5,000 开始,将大小增加 1,000,同时将访问项目的总数保持在 100,000,000。结果可以查到here.

结果令人惊讶。对于列表的某些大小(8,00011,00012,00013,00017,000 项),array_filter() 解决方案需要 比使用 array_reduce() 的任何解决方案花费的时间 多大约 10 倍。而对于其他的list size,回溯到3秒左右完成1亿节点访问,而其他方案需要的时间随着list长度的增加而不断增加。

我怀疑 array_filter() 解决方案所需时间的跳数的罪魁祸首是 PHP 的内存分配策略。对于某些长度的初始数组,array_column()array_filter() 返回的临时数组可能比其他大小触发更多的内存分配和垃圾清理周期。当然,同样的行为也可能发生在我没有测试过的其他尺寸上。

列表中 16,000...17,000 项附近的某处,我的解决方案开始 运行 比使用 array_reduce() 的@deceze 解决方案更快,并且在 25.000 附近它开始执行的速度与foreach 解决方案(有时甚至更快)。

对于长于 16,000-17,000 项的列表,array_filter() 解决方案始终需要比其他解决方案更多的时间来完成。


可以找到基准代码here。不幸的是,对于大于 15,000 元素的列表,它无法在 3v4l.org 上执行,因为它达到了系统施加的内存限制。

可以找到大于 5,000 项的列表的结果 here

代码是在 Linux Mint 18.1 上使用 PHP 7.0.20 CLI 执行的。不涉及 APC 或其他类型的缓存。

结论

对于最多 5,000 项的小型列表,请使用 array_filter(array_column()) 解决方案,因为它对于这种大小的列表表现良好,而且看起来很整洁。

对于大于 5,000 项的列表,切换到 foreach 解决方案。它看起来不太好,但运行速度很快,而且不需要额外的内存。随着列表大小的增加坚持下去。

对于编程、面试和让您的同事看起来很聪明,请使用任何 array_reduce() 解决方案。它显示了您对 PHP array functions and your understanding of the "callback" 编程概念的了解。