PHP如何确定foreach循环中的第一个和最后一个迭代?

613

这个问题很简单。我在我的代码中有一个foreach循环:

foreach($array as $element) {
    //code
}
在这个循环中,我想在第一次或最后一次迭代时有不同的反应。如何实现?

你应该使用PHP更新问题。Javascript也有forEach循环。观众可能会得到误导性的答案。 - Maidul
@Maidul 确实,在谷歌上并不清楚它是关于 PHP 的,所以我在标题中添加了“PHP”一词以增加清晰度。 - Hasen
21个回答

1265

如果您希望一种不需要在循环外部初始化计数器的解决方案,那么可以将当前迭代键与函数进行比较,该函数告诉您数组的最后/第一个键。

PHP 7.3及更高版本:

foreach ($array as $key => $element) {
    if ($key === array_key_first($array)) {
        echo 'FIRST ELEMENT!';
    }

    if ($key === array_key_last($array)) {
        echo 'LAST ELEMENT!';
    }
}

PHP 7.2及更早版本:

PHP 7.2已经到达生命周期结束(EOL),因此此处仅用于历史参考。建议避免使用。

foreach ($array as $key => $element) {
    reset($array);
    if ($key === key($array)) {
        echo 'FIRST ELEMENT!';
    }

    end($array);
    if ($key === key($array)) {
        echo 'LAST ELEMENT!';
    }
}

54
太棒了!比使用一堆数组要干净得多。 - Paul J
15
这个答案是正确的,应该得到高度重视。使用这些函数的另一个优点是,数组保持不变,以防在稍后需要它们。因为纯粹分享知识而给你点赞。 - Awemo
13
如果你想保持代码整洁,那么这绝对是最好的方法。我本来想点赞它的,但是现在我不确定使用数组方法带来的函数开销是否值得。如果我们只谈论最后一个元素,那么在每次循环迭代中执行的方法是 end() + key() - 如果两个都包含,那么每次循环需要调用4个方法。尽管如此,这些操作非常轻量级,并且可能只是指针查找,但是文档确实继续指定 reset()end()修改数组的内部指针 - 所以它比计数器更快吗?可能不是。 - pospi
19
我认为你不应该在foreach循环内部使用reset($array)。根据官方文档(www.php.net/foreach):“由于foreach依赖于内部数组指针的变化,因此在循环内部更改指针可能会导致意外行为。”而reset恰好会这样做(www.php.net/reset):“将数组的内部指针设置为第一个元素”。 - Gonçalo Queirós
12
可以的。foreach 方法会在数组的一个副本上进行操作,因此是可行的。但如果您还有顾虑的话,可以将 reset() 方法调用放在 foreach 之前,并将结果缓存到 $first 中。 - Rok Kralj
显示剩余12条评论

514

你可以使用一个计数器:

$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}

29
我认为在这里不应该进行反对投票,因为它也正常运作,并且仍然不像使用array_shiftarray_pop那样糟糕。虽然如果我必须实现这样一件事情,这就是我想出来的解决方案,但现在我会坚持Rok Kralj的答案。 - shadyyx
17
如果我需要一个计数器,我更倾向于使用 FOR 循环而不是 FOREACH。 - rkawano
17
如果你使用 $i = 1,就不必担心 $len - 1,只需使用 $len - aksu
2
@Twan 第三点怎么正确呢?或者说它与这个问题有什么关系,因为它涉及到HTML?这显然是一个PHP问题...当涉及到标记语义时,它要比“一个适当的blahblah总是比blahblah好(这甚至不是我的观点,这是纯粹的事实)”更深入的事实。 - Deji
2
@rkawano,但如果您使用FOR循环,则无法获取命名键 - Fahmi
显示剩余7条评论

138

我发现以下代码每次都能找到最后一个项目:

foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}

2
这个赞数太少了,使用它有什么缺点吗? - Kevin Kuyl
11
警告:该函数可能会返回布尔值 **FALSE**,但也可能返回一个被评估为 FALSE 的非布尔值。请阅读有关布尔值的章节以获取更多信息。在测试此函数的返回值时,请使用 === 运算符。 - Pang
2
@Kevin Kuyl - 正如Pang所述,如果数组包含PHP评估为false的项(即0,“”,null),则此方法将产生意外结果。我已经修改了代码以使用===。 - Eric Kigathi
4
这大部分非常棒,但为了澄清其他人指出的问题,它将无法处理像[true,true,false,true]这样的数组。但就个人而言,只要处理不包含布尔值false的数组,我将随时使用它。 - But those new buttons though..
10
在foreach循环内部绝对不应该使用next()函数,它会破坏内部数组指针。请查看文档以获取更多信息。 - Ian Hazzard
显示剩余4条评论

104

以下是更简化的版本,假设您没有使用自定义索引...

$len = count($array);
foreach ($array as $index => $item) {
    if ($index == 0) {
        // first
    } else if ($index == $len - 1) {
        // last
    }
}

版本2 - 因为我已经厌倦了除非必要否则使用else。

$len = count($array);
foreach ($array as $index => $item) {
    if ($index == 0) {
        // first
        // do something
        continue;
    }

    if ($index == $len - 1) {
        // last
        // do something
        continue;
    }
}

10
这也适用于对象。其他解决方案仅适用于数组。 - Yasen
1
这对我来说是最好的答案,但应该被压缩,没有必要在foreach循环之外声明长度:如果($index == count($array)-1){ ... } - Andrew
4
@Andrew 这样你可以在每次迭代时继续计算数组的元素数量。 - pcarvalho
2
@peteroak 是的,实际上这会对性能造成影响,具体取决于你计算什么或循环次数有多少,可能会相当显著。所以请忽略我的评论:D - Andrew
4
数组中的元素总数以属性形式存储在内部,因此使用if ($index == count($array) - 1)不会影响性能。请参见此处 - Katrina
@GreeKatrina 不错的发现!既然性能不一定是问题,那么剩下的就取决于个人喜好了。 - Hayden

43
你可以移除数组的第一个和最后一个元素,并将它们单独处理。
像这样:

代码

<?php
$array = something();
$first = array_shift($array);
$last = array_pop($array);

// do something with $first
foreach ($array as $item) {
 // do something with $item
}
// do something with $last
?>

将所有格式设置为CSS而不是内联标签可以改善您的代码并加快加载时间。

尽可能避免将HTML与PHP逻辑混合。

通过分离像这样的内容,可以使您的页面更易读和可维护:

<?php
function create_menu($params) {
  //retrieve menu items 
  //get collection 
  $collection = get('xxcollection') ;
  foreach($collection as $c) show_collection($c);
}

function show_subcat($val) {
  ?>
    <div class="sub_node" style="display:none">
      <img src="../images/dtree/join.gif" align="absmiddle" style="padding-left:2px;" />
      <a id="'.$val['xsubcatid'].'" href="javascript:void(0)" onclick="getProduct(this , event)" class="sub_node_links"  >
        <?php echo $val['xsubcatname']; ?>
      </a>
    </div>
  <?php
}

function show_cat($item) {
  ?>
    <div class="node" >
      <img src="../images/dtree/plus.gif" align="absmiddle" class="node_item" id="plus" />
      <img src="../images/dtree/folder.gif" align="absmiddle" id="folder">
      <?php echo $item['xcatname']; ?>
      <?php 
        $subcat = get_where('xxsubcategory' , array('xcatid'=>$item['xcatid'])) ;
        foreach($subcat as $val) show_subcat($val);
      ?>
    </div>
  <?php
}

function show_collection($c) {
  ?>
    <div class="parent" style="direction:rtl">
      <img src="../images/dtree/minus.gif" align="absmiddle" class="parent_item" id="minus" />
      <img src="../images/dtree/base.gif" align="absmiddle" id="base">
      <?php echo $c['xcollectionname']; ?>
      <?php
        //get categories 
        $cat = get_where('xxcategory' , array('xcollectionid'=>$c['xcollectionid']));
        foreach($cat as $item) show_cat($item);
      ?>
    </div>
  <?php
}
?>

我喜欢这个 last 的想法(从数组中删除最后一个元素),这样我就可以在不必在每个循环中检查的情况下以不同的方式生成最后一个元素。 - massimoi

21

简单来说,这个很有效!

// Set the array pointer to the last key
end($array);
// Store the last key
$lastkey = key($array);  
foreach($array as $key => $element) {
    ....do array stuff
    if ($lastkey === key($array))
        echo 'THE LAST ELEMENT! '.$array[$lastkey];
}

感谢@billynoah解决了最后问题。


3
好的!我只会澄清if ($key === $lastkey)这行代码。 - Krzysztof Przygoda
2
这不应该是 if ($lastkey === $key) 吗? - Kinetic
1
我收到了以下警告信息:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1 - But those new buttons though..
1
@Sydwell - 请仔细阅读错误信息。key()接收的是一个整数,而不是end()end()“返回最后一个元素的值”,而key()需要输入一个数组。 - But those new buttons though..
1
http://sandbox.onlinephpfunctions.com/code/8a532c9475049d05755f152ec98e1d06a5bc2ef2 - But those new buttons though..
显示剩余3条评论

21

尝试寻找第一个的方法是:

$first = true; 
foreach ( $obj as $value )
{
  if ( $first )
  {
    // do something
    $first = false; //in order not to get into the if statement for the next loops
  }
  else
  {
    // do something else for all loops except the first
  }
}

3
请编辑您的答案,添加有关您的代码如何工作以及如何解决OP的问题的说明。许多 Stack Overflow 的发布者是新手,可能无法理解您发布的代码。 - i alarmed alien
4
这个回答并没有说明如何判断是否处于循环的最后一次迭代。然而,它是一个有效的回答尝试,不应该被标记为“不是一个回答”。如果你不喜欢它,应该给它一个负评而不是标记它。 - ArtOfWarfare
很明显,在第一次迭代中,它将进入第一个条件,然后将其值更改为false,这样它只会进入第一次迭代一次。 - Mohamed23gharbi

13

1:为什么不使用简单的for语句?假设您正在使用真实数组而不是Iterator,您可以轻松检查计数器变量是否为0或比整个元素数量少1。在我看来,这是最清晰易懂的解决方案...

$array = array( ... );

$count = count( $array );

for ( $i = 0; $i < $count; $i++ )
{

    $current = $array[ $i ];

    if ( $i == 0 )
    {

        // process first element

    }

    if ( $i == $count - 1 )
    {

        // process last element

    }

}

2:你应该考虑使用嵌套集来存储你的树形结构。此外,你可以通过使用递归函数来改进整个过程。


如果你要使用 for 循环,可以从 1 循环到 n-1,并将 if 语句移出循环体。重复检查它们没有意义。 - mpen

9

最佳答案:


$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

foreach ($arr as $a) {

// This is the line that does the checking
if (!each($arr)) echo "End!\n";

echo $a."\n";

}

2
当数组中只有一个元素时,这个操作会失败。 - Memochipan
4
只要确保数组中始终有一个以上的元素(正如Memochipan所说),这个过程就很快速且简单。因此对于我来说,这不是一种可靠的解决方案 - 没有“最佳答案”。 - Seika85
5
"each() 函数在 PHP 7.2.0 版本之后将被弃用。" 请参考 http://php.net/manual/zh/function.each.php。 - Flow

8

最有效的答案来自@morg,与foreach不同,它仅适用于适当的数组,而不是哈希映射对象。该答案避免了在循环的每次迭代中进行条件语句的开销,如大多数答案(包括被接受的答案)具体地处理第一个和最后一个元素,并循环处理中间元素。

array_keys函数可用于使高效答案像foreach一样工作:

$keys = array_keys($arr);
$numItems = count($keys);
$i=0;

$firstItem=$arr[$keys[0]];

# Special handling of the first item goes here

$i++;
while($i<$numItems-1){
    $item=$arr[$keys[$i]];
    # Handling of regular items
    $i++;
}

$lastItem=$arr[$keys[$i]];

# Special handling of the last item goes here

$i++;

我没有对此进行基准测试,但循环中没有添加逻辑,这是性能最受影响的地方,所以我认为提供有效答案的基准测试结果非常接近。
如果您想将其功能化,我尝试了迭代列表函数。不过,如果您非常关心效率,可能需要对要点代码进行基准测试。我不确定所有函数调用引入了多少开销。

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