Как определить первую и последнюю итерацию в цикле foreach?

вопрос простой. У меня есть foreach цикл в моем коде:

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

в этом цикле я хочу реагировать по-разному, когда мы находимся в первой или последней итерации.

Как это сделать?

18 ответов


вы можете использовать счетчик:

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

Если вы предпочитаете решение, которое не требует инициализации счетчика вне цикла, я предлагаю сравнить текущий ключ итерации с функцией, которая сообщает вам последний / первый ключ массива.

это становится несколько более эффективным (и более читаемым) с предстоящим PHP 7.3.

решение для 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:

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

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

чтобы найти последний элемент, я нахожу, что этот фрагмент кода работает каждый раз:

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

более упрощенная версия выше и если вы не используете пользовательские индексы...

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

вы можете удалить первый и последний элементы из массива и обработать их отдельно.
Вот так:

<?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) {
  //retirive 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
}
?>

попытка найти первый будет:

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

просто это работает!

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

1: Почему бы не использовать простой for заявление? Предполагая, что вы используете реальный массив, а не Iterator вы можете легко проверить, является ли переменная счетчика 0 или на один меньше, чем все количество элементов. На мой взгляд, это самое чистое и понятное решение...

$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: Вы должны рассмотреть, используя Вложенные Наборы для хранения древовидной структуры. Кроме того, вы можете улучшить все это, используя рекурсивные функции.


лучший ответ:

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

}

для сценариев генерации SQL-запросов или всего, что делает другое действие для первого или последнего элементов, намного быстрее (почти в два раза быстрее), чтобы избежать использования ненужных проверок переменных.

текущее принятое решение использует цикл и проверку в цикле, который будет сделан every_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: 100000 работает модели морг

время: 1869.3430423737 миллисекунды

test2: 100000 запусков модели, если последний

время: 3235.6359958649 миллисекунд

и, таким образом, совершенно ясно, что проверка стоит много, и, конечно, становится еще хуже, чем больше переменных проверок вы добавляете ;)


самый эффективный ответ от @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++;

Я не делал бенчмаркинг на этом, но никакой логики не было добавлено к циклу, который был самым большим ударом по производительности, поэтому я подозреваю, что критерии, обеспеченные эффективным ответом, довольно близки.

если вы хотите functionalize такого рода вещи, я замахнулся на такой функция iterateList здесь. Хотя, возможно, вы захотите проверить код gist, если вы очень обеспокоены об эффективности. Я не уверен, сколько накладных расходов представляет все вызов функции.


использование булевой переменной по-прежнему является наиболее надежным, даже если вы хотите проверить первый внешний вид $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 что вернет true если нет next() значения для перебора.

и я предпочитаю использовать for петли вместо foreach если бы я собирался использовать счетчик, как это:

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

С ключами и значениями это также работает:

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

я наткнулся на эту ветку, когда у меня такая же проблема. Мне нужно только получить первый элемент, затем я повторно анализирую свой код, пока это не пришло мне в голову.

$firstElement = true;

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

вышеуказанные коды велики и завершены, но если вам нужен только первый элемент, вы можете попробовать этот код.


не уверен, что это все еще необходимо. Но следующее решение должно работать с итераторами и не требует 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;
        }
    }
}

вы также можете использовать анонимную функцию:

$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).
  • любые переменные извне анонимной функции вам нужно внутри, вы должны будете перечислить их рядом с $indexOfLastElement внутри use построить, опять же по ссылке, если необходимо.

вы можете использовать счетчик и длина массива.

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

попробуйте это:

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