从数组中选择每第n个项目

17

如何从大型数组中选择每个第n项是最有效的方式?有没有“聪明”的方法还是循环是唯一的方法?

需要考虑以下几点:

  • 该数组非常大,具有130,000个项目
  • 我必须选择每205个项目
  • 这些项目没有数值索引,因此for($i = 0; $i <= 130000; $i += 205)不起作用

到目前为止,这是我想出的最有效的方法:

$result = array();
$i = 0;
foreach($source as $value) {

    if($i >= 205) {
        $i = 0;
    }

    if($i == 0) {
        $result[] = $value;
    }

    $i++;
}

或者使用模运算:

$result = array();
$i = 0;
foreach($source as $value) {
    if($i % 205 == 0) {
        $result[] = $value;
    }
    $i++;
}

这些方法可能会很慢,有没有什么方法可以改进?还是我只是在纠结小细节吗?
编辑
各位都给出了合适的答案并附有适当的解释,我选择了最合适的答案作为采纳答案。谢谢!

这对我来说看起来很合理 - 你确定那段代码会导致瓶颈吗?如果不确定,可以进行性能分析!需要多长时间? - Dominic Rodger
@Dominic,这不是一个严重的瓶颈,只是一个我找不到合适解决方案的有趣问题。我并不认为一个“正确”的答案能节省多少毫秒的执行时间,但知道这个答案会很不错。 :) - Tatu Ulmanen
10个回答

17

使用foreach循环可通过比较测试快速迭代您的大型数组。除非有人希望通过循环展开来解决问题,否则建议使用类似于您所拥有的东西。

这个答案应该运行得更快。

$result = array();
$i = 0;
foreach($source as $value) {
    if ($i++ % 205 == 0) {
        $result[] = $value;
    }
}

我没时间测试,但如果你先对数组进行数值索引,可能可以使用 @haim 解决方案的变体。 值得尝试,看看是否能比我之前的解决方案获得任何收益:

$result = array();
$source = array_values($source);
$count = count($source);
for($i = 0; $i < $count; $i += 205) {
    $result[] = $source[$i];
}

这将在很大程度上取决于函数array_values的优化程度。它可能表现得非常糟糕。


非常好的解释,实际上是一种更快地完成我尝试做的事情的方法(只是稍微快一点,但无论如何都更快)。谢谢! - Tatu Ulmanen
@Tatu,在一个包含20万个元素的测试数组上,我进行了array_push和[]的快速比较测试,结果发现[]的运行速度大约是array_push的两倍。我修改了我的答案,建议你按照最初的方式使用赋值操作符。 - Corey Ballou
1
@Tatu,我快速对上述两种方法进行了基准测试,并发现当起始数组的平均大小大于50,000个结果时,第二种方法更快。 不过,我只有时间测试由array_fill()和range()生成的数值索引数组作为起点的情况。 - Corey Ballou
@CoreyBallou 这是最好的答案。不过,有没有可能显示详细信息时同时显示行号? - Moeez

8

会研究一下,我没有接受这个答案的唯一原因是我需要显著改变我的代码库以有效支持ArrayObjects。但已经注意并加分了。 - Tatu Ulmanen

6

我建议使用 array_slice

$count = count($array) ;
for($i=205;$i<$count;$i+=205){
    $result[] = array_slice($array,$i,1);
}

如果你的数组是按数字顺序索引的,那么这将非常快速:
$count = count($array) ;
for($i=205;$i<$count;$i+=205){
    $result[] = $array[$i];
}

1
至少语法更清晰了,你有什么想法,这会不会真的提高性能? - Tatu Ulmanen
1
循环语句速度较慢。您的count()函数也非常缓慢。 - Corey Ballou
把计数器放到循环外确实可以显著加快速度。 - John Parker
2
@cballou,但请注意,这种方法只需处理130000 / 205循环,而foreach需要遍历全部130000个项目。我需要测试它,我担心array_slice的性能,因为它可能会在内部从0到$i进行循环。 - Tatu Ulmanen
2
在更大的数组上,这个速度真的很慢。如果你把 $result=array_slice(...) 改成 $result[] = $array[$i],它比被接受的解决方案更快。 - Kevin
显示剩余2条评论

2
我认为解决这个问题的方法并不在于 PHP 语法,而是在于代码设计。
可以将数组编号,保持每205个项目一次性搜索数组,或仅搜索一次(缓存每205个项目列表)。
在我看来,跟踪每205个项目会更容易实现。您只需在数据库中计算所有项目的数量,并每次添加项目时检查数量的模数。如果有另一个第205个项目,则将其添加到数组中。但是当删除项目时,这可能会更棘手。您可能必须重新检查整个数组以重新对齐所有第205个项目。
如果您可以从已删除的项目处开始并向前移动,则执行此操作会更简单,但这仅适用于数字索引数组 - 如果是这样,您根本无需向前移动,只需进行一些数学运算即可重新计算它。
  • 数字索引-长期解决方案,但难以实现
  • 跟踪-更易于实现,但在删除项目时需要再次修改
  • 缓存项目-您应该为其他两个解决方案执行此操作,但单独使用它会很快,直到修改数组为止,此时您可能必须重新执行它。

1

如果这确实是瓶颈,您可能需要重新考虑设计,以使其具有数字索引。

编辑:或者创建并维护一个仅包含第205个项目的单独数组(在插入时进行更新或类似操作)。


0

这段代码可以从数组中选择所有偶数/奇数索引元素

<?php
$a = [1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four', 7 => 'Seven', 8 => 'Eight'];
echo "<br>";
foreach ($a as $key => $value) {

    if ($key % 2 == 0) {
        echo $value . ' ';
    }
}

0

似乎一次不能移动数组指针超过一次。我个人会使用以下方法:

reset($source);
$next = true;
while($next === true){
    $result[] = current($source);
    for(i=0;i<205;i++){
        $next = next($source);
    }
}

如果有人能找到一个可以一次移动数组指针超过一个步骤的函数,那么你会得到一个更好的答案。不过我认为这个已经很好了。


你说得对,每次移动指针205个位置就是解决方案了,否则这些都只是不同语法的相同东西,我想。 - Tatu Ulmanen
1
看我的答案。ArrayIterator::seek可以将指针移动到指定位置。 - Gordon
如果存在不会被评估为 true 的值怎么办? - Gumbo
当 $next 为 false 时,我们已经到达了数组的末尾。 - Gausie
@Gausie:...或者如果下一个元素不能被评估为true:“返回由内部数组指针指向的下一个位置的数组值,如果没有更多元素,则返回FALSE。” - Gumbo
三个等号可以停止这种情况的发生。请访问http://php.net/manual/en/function.next.php#function.next.returnvalues。 - Gausie

0
  • 创建一个二维数组 [205][N]
  • 将数据加载到数组中
  • 访问每个 N 的第 205 个元素

听起来可能有点傻,但根据定义它是最快的,因为你直接访问内存位置而不执行任何比较。


0

php 7.4+

$result = array_filter($source, fn(int $key): bool => $key % 205 === 0, ARRAY_FILTER_USE_KEY);

-1
你可以使用 array_keys 只处理数组的键。
$keys = array_keys($array);
for ($i=0, $n=min(count($keys), 130000); $i<$n; $i += 205) {
    $result[] = $array[$keys[$i]];
}

3
如果数组遍历是瓶颈,那么获取所有键的检索也会是瓶颈吗? - xtofl
@xtofl:PHP中的数组不像其他语言中的真正的数组。它们是用哈希表实现的,而键值则可能单独存储在链表中。 - Gumbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接