关于php:如何确定foreach循环中的第一次和最后一次迭代?

How to determine the first and last iteration in a foreach loop?

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

1
2
3
foreach($array as $element) {
    //code
}

在这个循环中,我希望在第一次或最后一次迭代中有不同的反应。

如何做到这一点?


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

在即将到来的PHP7.3中,这变得更加高效(和更可读)。

PHP 7.3及以上版本的解决方案:

1
2
3
4
5
6
7
foreach($array as $key => $element) {
    if ($key === array_key_first($array))
        echo 'FIRST ELEMENT!';

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

所有PHP版本的解决方案:

1
2
3
4
5
6
7
8
9
foreach($array as $key => $element) {
    reset($array);
    if ($key === key($array))
        echo 'FIRST ELEMENT!';

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


您可以使用计数器:

1
2
3
4
5
6
7
8
9
10
11
$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}


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

1
2
3
4
5
foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}


更简单的版本,假设您没有使用自定义索引…

1
2
3
4
5
6
7
8
$len = count($array);
foreach ($array as $index => $item) {
    if ($index == 0) {
        // first
    } else if ($index == $len - 1) {
        // last
    }
}

第二版-因为我不喜欢在必要的时候使用其他版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$len = count($array);
foreach ($array as $index => $item) {
    if ($index == 0) {
        // first
        // do something
        continue;
    }

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


您可以从数组中删除第一个和最后一个元素,然后分别处理它们。这样地:

1
2
3
4
5
6
7
8
9
10
11
<?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逻辑混合在一起。通过将以下内容分开,可以使您的页面更易于阅读和维护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
function create_menu($params) {
  //retirive menu items
  //get collection
  $collection = get('xxcollection') ;
  foreach($collection as $c) show_collection($c);
}

function show_subcat($val) {
  ?>
   
      <img src="../images/dtree/join.gif" align="absmiddle" style="padding-left:2px;" />
     
        <?php echo $val['xsubcatname']; ?>
     
   
  <?php
}

function show_cat($item) {
  ?>
   
      <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);
      ?>
   
  <?php
}

function show_collection($c) {
  ?>
   
      <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);
      ?>
   
  <?php
}
?>

试图找到第一个的方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
$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
  }
}


这很简单!

1
2
3
4
5
6
7
8
9
// 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为您解决最终问题。


1:为什么不使用一个简单的for语句?假设您使用的是实数数组而不是Iterator,您可以很容易地检查计数器变量是0还是小于元素总数的1。在我看来,这是最清晰和最可理解的解决方案…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$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:应该考虑使用嵌套集来存储树结构。此外,还可以使用递归函数来改进整个过程。


最佳答案:

1
2
3
4
5
6
7
8
9
10
11
12
$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."
"
;

}


@morg提供的最有效的答案与foreach不同,只适用于适当的数组,而不是散列映射对象。这个答案避免了循环每次迭代的条件语句的开销,就像在大多数这些答案(包括接受的答案)中一样,具体地处理第一个和最后一个元素,并在中间元素上循环。

使用array_keys函数可以使有效答案像foreach那样工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$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++;

我还没有在这方面做基准测试,但是循环中没有添加任何逻辑,这是性能发生的最大冲击,所以我怀疑提供有效答案的基准非常接近。

如果你想实现这类功能,我在这里讨论了一个迭代列表函数。尽管如此,如果您非常关心效率,那么您可能需要对gist代码进行基准测试。我不确定所有函数调用都会带来多少开销。


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

当前接受的解决方案使用循环和循环内的检查,每次迭代都要进行检查,正确(快速)的方法如下:

1
2
3
4
5
6
7
8
9
10
$numItems = count($arr);
$i=0;
$firstitem=$arr[0];
$i++;
while($i<$numItems-1){
    $some_item=$arr[$i];
    $i++;
}
$last_item=$arr[$i];
$i++;

一个自制的小基准显示了以下内容:

试验1:100000次模型试验

时间:1869.3430423737毫秒

测试2:100000次模型运行(如果最后一次)

时间:3235.6359958649毫秒

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


对于键和值,这也适用:

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


使用布尔变量仍然是最可靠的,即使您想检查$value的第一个外观(我发现它在我的情况和许多情况下更有用),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$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()的迭代值,那么用!next( $array )来查找最后一个$value,它将返回true

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

1
2
3
4
5
6
7
8
9
10
11
$len = count( $array );
for ( $i = 0; $i < $len; $i++ ) {
    $value = $array[$i];
    if ($i === 0) {
        // first
    } elseif ( $i === $len - 1 ) {
        // last
    }
    // …
    $i++;
}

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

1
2
3
4
5
6
7
$firstElement = true;

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

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


不确定是否还有必要。但是下面的解决方案应该与迭代器一起工作,不需要count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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;
        }
    }
}

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

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

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


您也可以使用匿名函数:

1
2
3
4
5
6
7
8
9
10
$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,您必须通过引用(&$element)。
  • 任何来自内部匿名函数外部的变量,都必须在use构造内部的$indexOfLastElement旁边列出,如果需要,也可以通过引用列出。

试试这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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) . '">' . $child['name'] . '
</li>
'
;
      $class = array();
      $list .= children($parents, $child['id'], $selected);
    }
    $list .= '
</ul>
'
;
    return $list;
  }
}
$output .= children( $parents, 0, $p_industry_id);