在 PHP 中迭代数组时,如何预先查看元素?

30

在PHP 5.2中,当使用foreach迭代数组时,是否可以“预览”数组的下一个元素?例如,我经常使用foreach来操作数组中的数据:

foreach($array as $object) {
  // do something
}

我经常需要在遍历数组时查看下一个元素,我知道可以使用for循环并通过索引引用下一个元素($array[$i+1]),但这对关联数组不起作用。 是否有任何优雅的解决方案来解决我的问题,也许涉及SPL?

6个回答

58
您可以使用CachingIterator来实现这个目的。
以下是一个示例:
$collection = new CachingIterator(
                  new ArrayIterator(
                      array('Cat', 'Dog', 'Elephant', 'Tiger', 'Shark')));

CachingIterator 始终比内部迭代器慢一步:

var_dump( $collection->current() ); // null
var_dump( $collection->getInnerIterator()->current() ); // Cat

因此,当您通过foreach遍历$collection时,内部ArrayIterator的当前元素将是下一个元素,这使您能够窥视它:

foreach($collection as $animal) {
     echo "Current: $animal";
     if($collection->hasNext()) {
         echo " - Next:" . $collection->getInnerIterator()->current();
     }
     echo PHP_EOL;
 }

将输出:

Current: Cat - Next:Dog
Current: Dog - Next:Elephant
Current: Elephant - Next:Tiger
Current: Tiger - Next:Shark
Current: Shark
由于某种我无法解释的原因,CachingIterator将总是尝试将当前元素转换为字符串。如果您想迭代对象集合并需要访问属性和方法,请在构造函数的第二个参数中传递CachingIterator :: TOSTRING_USE_CURRENT。
另外,CachingIterator得名于其缓存其迭代过的所有结果的能力。要使此功能工作,您必须使用CachingIterator :: FULL_CACHE实例化它,然后可以使用getCache()获取缓存的结果。

1
+1 我甚至不知道这些([x]迭代器)的存在,非常有用,特别是DirectoryIterator。下次我处理文件时,这将为我节省大量工作。谢谢 :) - Psytronic
2
@Psytronic 这些真的很不错。堆叠它们的能力可以实现非常酷和灵活的功能。不幸的是,它们文档记录不太全面,但是你可以看看http://www.phpro.org/tutorials/Introduction-to-SPL.html - Gordon
不幸的是,如果数组包含对象而不是字符串,则该解决方案无法工作。 我会收到以下异常:Catchable fatal error: Object of class MySampleClass could not be converted to string in /home/www/test.php on line 398 - pako
2
@pako 要么在 MySampleClass 中实现 __toString 方法,要么在 CachingIterator 构造函数的第二个参数中传递 CachingIterator::TOSTRING_USE_CURRENT - Gordon
好的,我会尝试。但是为什么迭代器需要这一步呢?我希望它不会根据元素的字符串表示更改数组中的顺序。 - pako
2
@pako 我不知道为什么需要这一步,但显然确实需要。迭代器目前的文档很不完善。我提供的解决方案基于使用PHP的反射API和试错。如果您担心迭代器会执行不应该执行的操作,请通过单元测试进行确认。 - Gordon

20
使用array_keys函数。
$keys = array_keys($array);
for ($i = 0; $i < count($keys); $i++) {
    $cur = $array[$keys[$i]];
    $next = $array[$keys[$i+1]];
}

为什么我要接受那个复杂的答案来完成一个基本的任务,而这个答案已经在核心函数中了呢?谢谢。 - Noumenon
我喜欢这个答案,能够用数字引用项目有很多优点。 - Kelly Larsen

8
您可以使用 nextprev 来迭代一个数组。 current 返回当前项的值,key 返回当前键名。
因此,您可以像这样做:
while (key($array) !== null) {
    next($array); // set pointer to next element
    if (key($array) === null) {
        // end of array
    } else {
        $nextItem = current($array);
    }
    prev($array); // resetting the pointer to the current element

    // …

    next($array);
}

3
这段代码看起来相当复杂,而且容易出错(有太多的“next/prev”操作,可能会发生非常奇怪的事情……)。 - pako

4

我知道这是一篇旧文章,但现在我可以更好地解释当前/下一个/上一个的事情。例如:

$array = array(1,2,3,2,5);

foreach($array as $k => $v) {
    // in foreach when looping the key() and current() 
    // is already pointing to the next record
    // And now we can print current
    print 'current key: '.$k.' and value: '.$v;
    // if we have next we can print its information too (key+value)
    if(current($array)) {
         print ' - next key: '.key($array).' and value: '.current($array);
         // at the end we must move pointer to next
         next($array);
    }
    print '<br>';
}

// prints:
// current key: 0 and value: 1 - next key: 1 and value: 2
// current key: 1 and value: 2 - next key: 2 and value: 3
// current key: 2 and value: 3 - next key: 3 and value: 2
// current key: 3 and value: 2 - next key: 4 and value: 5
// current key: 4 and value: 5

如果$array中存在假值,我认为这个报告不会正确。 - grantwparks

0
我知道我可以使用for循环并通过索引引用下一个项目($array[$i+1]),但这对于关联数组不起作用。
考虑使用array_values()将您的关联数组转换为顺序索引数组,从而使您能够使用简单的for循环解决方案。

0

虽然这是一篇旧帖,但我还是想发表我的看法:

如果你试图预先查看,你真的需要问自己“我是否以最佳方式解决了这个问题。”

你可以在不进行预先查看的情况下解决所有预先查看的问题。你只需要在集合之前声明一个"$prevItem"引用,并将其初始化为null。每次循环结束时,将$prevItem设置为刚刚评估过的当前数组项。实际上,你不是在预先查看,而是从第二个项目开始执行你的实际逻辑,并使用$prevItem引用来执行操作。通过注意到$prevItem为null,你跳过了第一个项目。

$prevItem = null;
$prevKey = null;

foreach($collection as $key => $val)
{
     if($prevItem != null)
     {
          //do your operation here
     }


     $prevItem = $val;
     $prevKey = $key;
}

这是干净的代码,也是一种常见的模式。

在迭代过程中避免对底层数据结构进行操作... 这不是好的实践方法,而且你很少需要这样做。


在大多数情况下,你是正确的,但我现在正在开发一个应用程序,在其中需要在上一次迭代中显示关于下一次迭代的信息。我承认这是一种独特的情况,但确实存在这样的情况。 - Matthew
请提供您的情况示例。很少有情况是您无法重构循环以从前一个周期开始并查看先前的数据。我甚至没有见过这样的情况,它没有证明自己是一个薄弱的设计。 - JamesHoux
我这里没有简洁的代码示例可以粘贴,但我正在开发一个平台来优化家电送货路线。在一天的停靠列表中,我们需要提供有关B站的一些信息,这些信息与循环中的A站信息交织在一起。正是“交织”的部分使得这个问题棘手;否则,您之前在许多其他上下文中使用的$prevItem方法在这里同样适用。 - Matthew

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