使用PHP-SPL解决方案倒序迭代数组?

34

PHP中是否有SPL反向数组迭代器?如果没有,最好的实现方式是什么?

我可以简单地执行以下操作:

$array = array_reverse($array);
foreach($array as $currentElement) {}
或者
for($i = count($array) - 1; $i >= 0; $i--)
{

}

但是否有更优雅的方法?


5
你的第二种方法不一定正确,只适用于数字连续的键。 - AbiusX
1
哎呀,是的,没错。在我的情况下,我有一个数字数组,但是你的评论让我认为我的问题更加相关了。 - Sebastian Hoitz
2
数值数组不一定按顺序排列,也不一定从0开始。 - AbiusX
11个回答

78

这里是一个不复制也不修改数组的解决方案:

for (end($array); key($array)!==null; prev($array)){
  $currentElement = current($array);
  // ...
}

如果您也想引用当前键:

for (end($array); ($currentKey=key($array))!==null; prev($array)){
  $currentElement = current($array);
  // ...
}

这总是奏效的,因为 PHP 数组键永远不会为 null,并且比这里给出的任何其他答案都更快。


4
最佳答案在这里。谢谢! - mpen
这是一个正确的解决方案,用于遍历数组,以相反的顺序获取任何键(数字或非数字)和值(包括nulls)。 - FrancescoMM
1
OP 要求一个迭代器。 - Gordon
但是PHP当前仅支持“内部”迭代器(因此对OP的答案是“不可能”),但这应该没有问题,除非内部迭代器被意外地更改(由循环内部的某些内容)。 - Top-Master
然而,它仍然修改了数组,不是吗?它改变了内部数组指针。记录正常的“foreach”也会更改内部指针。 - Danon

17
$item=end($array);
do {
...
} while ($item=prev($array));

end将内部数组迭代器设置为最后一个元素并返回它。prev返回当前元素并将迭代器设置为前一个元素。 - AbiusX
实际上,第二个版本将跳过数组的最后一个元素。 - user312650
下降投票。第二个版本跳过了最后一个元素。 - thesmart
19
如果数组中的某个值为false、NULL或0,会怎么样? - dader

14

没有ReverseArrayIterator来实现这个功能。但是你可以这样做:

$reverted = new ArrayIterator(array_reverse($data));

或者将其转化为您自己的自定义迭代器,例如:

class ReverseArrayIterator extends ArrayIterator 
{
    public function __construct(array $array)
    {
        parent::__construct(array_reverse($array));
    }
}

以下是稍微长一些的实现方式,它不使用 array_reverse,而是通过标准的数组函数对数组进行迭代:

class ReverseArrayIterator implements Iterator
{
    private $array;

    public function __construct(array $array)
    {
        $this->array = $array;
    }

    public function current()
    {
        return current($this->array);
    }

    public function next()
    {
        return prev($this->array);
    }

    public function key()
    {
        return key($this->array);
    }

    public function valid()
    {
        return key($this->array) !== null;
    }

    public function rewind()
    {
        end($this->array);
    }
}

7
建议使用array_reverse($array, true)来保留数字键的关联。 - dader
15
请记住,array_reverse会复制数组元素,因此在性能方面不是很好。 - AbiusX
1
array_reverse很耗费资源,我不建议每次迭代都这样做。最好从一开始就以相反的顺序存储东西或使用@linepogl的解决方案。 - Dan Bechard
@FrancescoMM 没有人要求一个优雅的解决方案。虽然它在过程化方法上没有太多的优势,但是OP明确要求了SPL迭代器。 - Gordon
@FrancescoMM 我承认我错了。不过,我觉得重点应该放在迭代器和SPL上,而不是优雅上,这就是为什么我提供了另一个迭代器的原因。 - Gordon
显示剩余3条评论

12

linepogl的回答的基础上,我想到了这个函数:

/**
 * Iterate an array or other foreach-able without making a copy of it.
 *
 * @param array|\Traversable $iterable
 * @return Generator
 */
function iter_reverse($iterable) {
    for (end($iterable); ($key=key($iterable))!==null; prev($iterable)){
        yield $key => current($iterable);
    }
}

用法:

foreach(iter_reverse($my_array) as $key => $value) {
    // ... do things ...
}

这适用于数组和其他可迭代对象,而不必先复制它。


很好,甚至可以使用C.M.的答案进行优化,避免current()调用。 - Gras Double
请注意,由于在函数内部我们正在更改内部指针,因此虽然PHP确保它不会影响函数外部的数组指针,但它实际上会创建一个数组副本。 - Gras Double

10

根据你要做的事情,你可能需要查看spl数据结构类,例如SplStack。SplStack实现了Iterator、ArrayAccess和Countable接口,因此它可以像数组一样使用,但默认情况下,它的迭代器按FILO顺序进行遍历。例如:

$stack = new SplStack();
$stack[] = 'one';
$stack[] = 'two';
$stack[] = 'three';

foreach ($stack as $item)
{
    print "$item\n";
}

这将打印

three
two
one

6
请注意,如果您想保留数组的键,则必须将true作为第二个参数传递给array_reverse函数:
$array = array_reverse($array, true);
foreach ($array as $currentElement) {
    // do something here
}

6
基于 linepogl 的回答... 你可以通过避免调用current()来使其效率更高。
for ($value = end($array); ($key = key($array)) !== null; $value = prev($array)) {
     // ... do something with $key => $value
}

5
$array = array_reverse($array);
foreach($array as $key => $currentElement) {}

这是更好的使用方式。如果键不是连续或整数,它也会处理好。


1
如果您不需要保留反转后的数组,可以将其缩短为foreach(array_reverse($array, true) ...。请记住array_reverse的第二个参数,它可以保留键。 - Charles

1
$array=array(
    0 => 0,
    '1' => 1,
    2 => null,
    3 => false
);

$value=end( $array );                      // ← value for first iteration
while(($key=key( $array )) !== null) {
  echo "key=$key, value=$value\n";

  $value=prev( $array );                   // ← value for next iteration
}

仅有代码堆积而没有解释很少有帮助。请考虑为您的答案添加一些上下文。 - Chris

0

这可能是一种更高效的方法,因为它不构造一个新数组。它还能很好地处理空数组。

$item = end($items);
while($item)
{
    ...do stuff...
    $item = prev($items);
}

6
如果其中一个项目的值为false或NULL或0,会怎样呢? - dader

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