【问题标题】:PHP How to determine the first and last iteration in a foreach loop?PHP如何确定foreach循环中的第一次和最后一次迭代?
【发布时间】:2023-01-01 16:37:35
【问题描述】:

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

foreach($array as $element) {
    //code
}

在这个循环中,我想在第一次或最后一次迭代时做出不同的反应。

这该怎么做?

【问题讨论】:

  • 您应该使用 PHP 更新问题。 Javascript 也有 forEach 循环。观众可能会得到误导性的答案。
  • @Maidul 是的,在谷歌中根本不清楚它是关于 PHP 的,所以为了清楚起见,我在标题中添加了“PHP”一词。

标签: php loops foreach


【解决方案1】:

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

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!';
    }
}

【讨论】:

  • 很棒的答案!比使用一堆数组要干净得多。
  • 这应该一直冒到顶部,因为它是正确的答案。与使用 array_shift 和 array_pop 相比,这些函数的另一个优点是数组保持原样,以备日后需要时使用。 +1 只是为了分享知识。
  • 如果您想保持代码整洁,这绝对是最好的方法。我正要赞成它,但现在我不相信这些数组方法的功能开销是值得的。如果我们只讨论最后一个元素,那么循环的每次迭代都是 end() + key() - 如果两者都是,那么每次都会调用 4 个方法。当然,这些将是非常轻量级的操作,可能只是指针查找,但随后文档继续指定 reset()end()调整数组的内部指针——它比计数器快吗?可能不会。
  • 我认为您不应该在 foreach 中发出 reset($array)。来自官方文档 (www.php.net/foreach):“由于 foreach 依赖于内部数组指针,因此在循环内更改它可能会导致意外行为。”重置正是这样做的(www.php.net/reset):“将数组的内部指针设置为其第一个元素”
  • @GonçaloQueirós:它有效。 Foreach 处理数组的副本。但是,如果您仍然担心,请随时将 reset() 调用移到 foreach 之前并将结果缓存在 $first 中。
【解决方案2】:

你可以使用一个计数器:

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

【讨论】:

  • 我认为不应该在这里进行投票,因为它也可以正常工作,并且仍然不像使用 array_shiftarray_pop 那样垃圾。虽然这是我想出的解决方案,如果我必须实施这样的事情,我会坚持Rok Kralj的现在回答。
  • 如果我需要一个计数器,那么我更喜欢使用 FOR 循环而不是 FOREACH。
  • 如果你使用$i = 1,你不用担心$len - 1,直接使用$len即可。
  • @Twan 第 3 点对吗?或者与这个问题完全相关,因为它涉及 HTML?很明显,这是一个 PHP 问题......当涉及到标记语义时,它归结为比“适当的废话总是比废话更好(这甚至不是我的意见,这是纯粹的事实)”更深刻的事实。
  • @rkawano 但你无法得到命名键如果你使用 FOR 循环
【解决方案3】:

为了找到最后一项,我发现这段代码每次都有效:

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

【讨论】:

【解决方案4】:

上面的更简化版本并假设您没有使用自定义索引......

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

【讨论】:

  • 这也适用于对象。其他解决方案仅适用于数组。
  • 这对我来说是最好的答案,但应该被压缩,没有必要在 foreach 循环之外声明长度:if ($index == count($array)-1){ ... }
  • @Andrew 这样你就可以在每次迭代中继续计算数组的元素。
  • @peteroak 是的,实际上它在技术上会损害性能,并且取决于您的计数或多少循环可能很重要。所以无视我的评论:D
  • @peteroak @Andrew 数组中元素的总数在内部存储为一个属性,因此执行if ($index == count($array) - 1) 不会对性能造成任何影响。见here
【解决方案5】:

您可以从数组中删除第一个和最后一个元素并分别处理它们。

像这样:

<?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
}
?>

【讨论】:

  • 我喜欢这个主意最后的(从数组中删除最后一个元素),这样我就可以以不同的方式生成最后一个元素,而无需在每个循环中进行检查。
【解决方案6】:

尝试找到第一个将是:

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

【讨论】:

  • 请编辑您的答案以添加对您的代码如何工作以及它如何解决 OP 问题的解释。许多 SO 发布者都是新手,不会理解您发布的代码。
  • 这个答案没有说明如何确定您是否处于循环的最后一次迭代中。但是,这是对答案的有效尝试,不应将其标记为不是答案。如果你不喜欢它,你应该投反对票,而不是标记它。
  • 很明显,在第一次迭代中他会进入第一个条件,然后将其值更改为 false ,这样它只会进入第一次迭代一次。
【解决方案7】:

这很有效!

// 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 整理结尾问题。

【讨论】:

  • 最好!我只会澄清if ($key === $lastkey)
  • 这不应该是if ($lastkey === $key)吗?
  • 我得到:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
  • @Sydwell - 阅读错误。 key() 得到一个整数,而不是 end()end()返回最后一个元素的值",key() 需要一个数组作为输入。
【解决方案8】:

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:你应该考虑使用Nested Sets来存储你的树结构。此外,您可以通过使用递归函数来改进整个过程。

【讨论】:

  • 如果你打算使用for,你可以从1循环到n-1,并将ifs从身体中取出。反复检查它们毫无意义。
【解决方案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!
";

echo $a."
";

}

【讨论】:

  • 当数组中只有一个元素时,此操作失败。
  • 只要您可以确定数组中总是有一个以上的元素(如 Memochipan 所说),它就会快速而简单。所以这对我来说不是一个故障安全解决方案——没有“最佳答案”。
  • each() 将从 PHP 7.2.0 开始弃用.另见php.net/manual/en/function.each.php
【解决方案10】:

来自 @morg 的 efficient answerforeach 不同,它仅适用于适当的数组,不适用于哈希映射对象。这个答案避免了循环的每次迭代的条件语句的开销,因为在大多数这些答案(包括接受的答案)中具体来说处理第一个和最后一个元素,并遍历中间元素。

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

我没有对此进行基准测试,但没有向循环中添加任何逻辑,这是对性能影响最大的情况,所以我怀疑提供有效答案的基准非常接近。

如果你想功能化这种事情,我就对这样一个iterateList function here挥了挥手。虽然,如果您非常关心效率,您可能想要对要点代码进行基准测试。我不确定所有函数调用会引入多少开销。

【讨论】:

    【解决方案11】:

    对于 SQL 查询生成脚本,或任何对第一个或最后一个元素执行不同操作的东西,避免使用不必要的变量检查要快得多(几乎快两倍)。

    当前接受的解决方案使用循环和循环内的检查,将在每个_single_iteration 中进行检查,正确(快速)的方法如下:

    $numItems = count($arr);
    $i=0;
    $firstitem=$arr[0];
    $i++;
    while($i<$numItems-1){
        $some_item=$arr[$i];
        $i++;
    }
    $last_item=$arr[$i];
    $i++;
    

    一个小小的自制基准显示如下:

    测试 1:模型 morg 运行 100000 次

    时间:1869.3430423737 毫秒

    test2:100000 次模型运行(如果是最后一次)

    时间:3235.6359958649 毫秒

    因此很明显,支票的成本很高,当然,您添加的可变支票越多,情况就越糟;)

    【讨论】:

    • 您的代码仅在您确定必须具有递增整数键时才有效。 $arr = array('one' =&gt; "1 1 1", 4 =&gt; 'Four', 1 =&gt; 'One'); $numItems = count($arr); $i=0; $firstitem=$arr[0]; echo $i . ': ' . $firstitem . ", "; $i++; while($i&lt;$numItems-1){ $some_item=$arr[$i]; echo $i . ': ' . $some_item . ", "; $i++; } $last_item=$arr[$i]; echo $i . ': ' . $last_item . ", "; $i++; 将输出:0: , 1: One, 2: ,
    • 将散列映射投射到数组是不可定义的行为,“对象”array(){'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"} 但空元素会被计数忽略,您正在计算已定义“事物”的数量!!赏金牺牲来了!这个答案值得赏金,但如果@Morg。走了,没有意义。我会赏金给一个可能不会再使用 SO 的人!如果他回来并改进他的答案,他应该得到赏金!
    • 正如 @mjz19910 所指出的,哈希映射和数组不可互换。但是,您可以使用 array_keys 函数获取散列的属性,您能够像数组一样对待。见my "improved" answer
    • 那就是我用来查询的:$querySet = ""; foreach ($fieldSet as $key=&gt;$value) { $value = $db-&gt;dbLink-&gt;quote($value); $querySet .= "$key = $value, "; } $querySet = substr_replace($querySet, "", -2); $queryString = "UPDATE users SET $querySet WHERE user_ID = '$user_ID'";
    【解决方案12】:

    使用 Keys 和 Values 这也适用:

    foreach ($array as $key => $value) {
        if ($value === end($array)) {
            echo "LAST ELEMENT!";
        }
    }
    

    【讨论】:

    • 这样你就可以比较值,如果一个数组包含两个相同的元素则不起作用。
    【解决方案13】:

    使用布尔变量仍然是最可靠的,即使你想检查 $value 的第一次出现(我发现它在我的情况和许多情况下更有用),像这样:

    $is_first = true;
    
    foreach( $array as $value ) {
        switch ( $value ) {
            case 'match':
                echo 'appeared';
    
                if ( $is_first ) {
                    echo 'first appearance';
                    $is_first = false;
                }
    
                break;
            }
        }
    
        if( !next( $array ) ) {
            echo 'last value';
        }
    }
    

    那么!next( $array )如何找到最后一个$value,如果没有要迭代的next()值,它将返回true

    如果我要使用计数器,我更喜欢使用 for 循环而不是 foreach,如下所示:

    $len = count( $array );
    for ( $i = 0; $i < $len; $i++ ) {
        $value = $array[$i];
        if ($i === 0) {
            // first
        } elseif ( $i === $len - 1 ) {
            // last
        }
        // …
        $i++;
    }
    

    【讨论】:

      【解决方案14】:

      当我遇到同样的问题时,我遇到了这个线程。我只需要获取第一个元素,然后重新分析我的代码,直到我想到这一点。

      $firstElement = true;
      
      foreach ($reportData->result() as $row) 
      {
             if($firstElement) { echo "first element"; $firstElement=false; }
             // Other lines of codes here
      }
      

      上面的代码很棒而且很完整,但是如果你只需要第一个元素,那么你可以试试这个代码。

      【讨论】:

        【解决方案15】:

        使用重置($数组)结束($数组)

        <?php
        
            $arrays = [1,2,3,4,5];
        
            $first  = reset($arrays);
            $last   = end($arrays);    
        
            foreach( $arrays as $array )
            {
        
                if ( $first == $array )
                {
                    echo "<li>{$array} first</li>";
                }
                else if ( $last == $array )
                {
                    echo "<li>{$array} last</li>";
                }
                else
                {
                    echo "<li>{$array}</li>";
                }                
        
            }
        

        Demo repl.it

        【讨论】:

          【解决方案16】:

          不确定是否还有必要。但以下解决方案应该适用于迭代器并且不需要 count

          <?php
          
          foreach_first_last(array(), function ($key, $value, $step, $first, $last) {
              echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
          });
          
          foreach_first_last(array('aa'), function ($key, $value, $step, $first, $last) {
              echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
          });
          echo PHP_EOL;
          
          foreach_first_last(array('aa', 'bb', 'cc'), function ($key, $value, $step, $first, $last) {
              echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
          });
          echo PHP_EOL;
          
          function foreach_first_last($array, $cb)
          {
              $next = false;
              $current = false;
              reset($array);
              for ($step = 0; true; ++$step) {
                  $current = $next;
                  $next = each($array);
                  $last = ($next === false || $next === null);
                  if ($step > 0) {
                      $first = $step == 1;
                      list ($key, $value) = $current;
                      if (call_user_func($cb, $key, $value, $step, $first, $last) === false) {
                          break;
                      }
                  }
                  if ($last) {
                      break;
                  }
              }
          }
          

          【讨论】:

            【解决方案17】:

            您也可以使用匿名函数:

            $indexOfLastElement = count($array) - 1;
            array_walk($array, function($element, $index) use ($indexOfLastElement) {
                // do something
                if (0 === $index) {
                    // first element‘s treatment
                }
                if ($indexOfLastElement === $index) {
                    // last not least
                }
            });
            

            还应该提到三件事:

            • 如果您的数组没有严格(数字)索引,您必须首先通过array_values 将您的数组通过管道传输。
            • 如果您需要修改 $element,您必须通过引用 (&amp;$element) 传递它。
            • 你需要在匿名函数之外的任何变量,你必须在 use 构造中将它们列在 $indexOfLastElement 旁边,如果需要,再次通过引用。

            【讨论】:

              【解决方案18】:

              您可以使用计数器和数组长度。

              $array = array(1,2,3,4);
              
                  $i = 0;
                  $len = 计数($数组);
                  foreach ($array as $item) {
                      如果($i === 0){
                          // 第一的
                      } else if ($i === $len - 1) {
                          // 最后的
                      }
                      // ...
                      $i++;
                  }

              【讨论】:

                【解决方案19】:
                foreach ($arquivos as $key => $item) {
                   reset($arquivos);
                   // FIRST AHEAD
                   if ($key === key($arquivos) || $key !== end(array_keys($arquivos)))
                       $pdf->cat(null, null, $key);
                
                   // LAST
                   if ($key === end(array_keys($arquivos))) {
                       $pdf->cat(null, null, $key)
                           ->execute();
                   }
                }
                

                【讨论】:

                  【解决方案20】:

                  最简单的方法是

                  $array = [9,5,6,4,7,8];
                  
                  $current_iteration = 0;
                  
                  foreach($array as $item){
                    if( 0 === $current_iteration ){
                      echo 'this is the first item: ' . $item;
                    }
                  
                    if( (count($array) - 1) === $current_iteration){
                      echo 'this is the last item: ' . $item;
                    }
                  
                    $current_iteration++;
                  }
                  

                  【讨论】:

                    【解决方案21】:

                    试试这个:

                    function children( &$parents, $parent, $selected ){
                      if ($parents[$parent]){
                        $list = '<ul>';
                        $counter = count($parents[$parent]);
                        $class = array('first');
                        foreach ($parents[$parent] as $child){
                          if ($child['id'] == $selected)  $class[] = 'active';
                          if (!--$counter) $class[] = 'last';
                          $list .= '<li class="' . implode(' ', $class) . '"><div><a href="]?id=' . $child['id'] . '" alt="' . $child['name'] . '">' . $child['name'] . '</a></div></li>';
                          $class = array();
                          $list .= children($parents, $child['id'], $selected);
                        }
                        $list .= '</ul>';
                        return $list;
                      }
                    }
                    $output .= children( $parents, 0, $p_industry_id);
                    

                    【讨论】: