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


494

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

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

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

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

Ответы:


426

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

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

24
Я не думаю, что здесь должно происходить понижение голосов, так как это также работает правильно и все еще не так глупо, как использование array_shiftи array_pop. Хотя это решение, которое я придумал, если бы мне пришлось реализовать такую ​​вещь, я бы теперь придерживался ответа Рок Краля .
shadyyx

15
Если мне нужен счетчик, то я предпочитаю использовать цикл FOR вместо FOREACH.
rkawano

10
Если вы используете $i = 1, вам не нужно беспокоиться $len - 1, просто используйте $len.
Аксу

2
@ Тван Как правильно пункт № 3? Или вообще имеет отношение к этому вопросу, поскольку он затрагивает HTML? Это вопрос PHP, ясно ... и когда дело доходит до семантики разметки, это сводится к гораздо более глубоким фактам, чем "настоящий бла-бла всегда лучше, чем бла-бла (это даже не мое мнение, это чистый факт)"
Дежи,

1
@rkawano, но вы не можете получить именованный ключ, если используете цикл FOR
Fahmi

1016

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

Это становится несколько более эффективным (и более читабельным) с выходом 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!';
}

44
Фантастический ответ! Намного чище, чем использовать кучу массивов.
Пол Дж

14
Это должно пузыриться до самого верха, потому что это правильный ответ. Другое преимущество этих функций по сравнению с использованием array_shift и array_pop состоит в том, что массивы остаются нетронутыми на случай, если они понадобятся позже. +1 для обмена знаниями только ради этого.
Awemo

13
это определенно лучший способ, если вы хотите сохранить свой код в чистоте. Я собирался объявить это, но теперь я не уверен, что функциональные издержки этих методов массива того стоят. Если мы говорим только о последнем элементе, то это end()+ key()на каждой итерации цикла - если это оба, то каждый раз вызывается 4 метода. Конечно, это были бы очень легкие операции и, вероятно, просто поиск по указателю, но затем документы продолжают указывать это reset()и end() изменяют внутренний указатель массива - так это быстрее, чем счетчик? возможно нет.
Поспи

19
Я не думаю, что вы должны выполнить сброс ($ массив) внутри foreach. Из официальной документации (www.php.net/foreach): «Поскольку foreach полагается на внутренний указатель массива, изменение его в цикле может привести к неожиданному поведению». И сброс делает именно это (www.php.net/reset): «Установить внутренний указатель массива на его первый элемент»
Гонсалу Кейрос

11
@ GonçaloQueirós: это работает. Foreach работает на копии массива. Однако, если вы все еще обеспокоены, не стесняйтесь переместить reset()вызов перед foreach и кэшировать результат в $first.
Рок Краль

121

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

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

2
Это имеет слишком мало голосов, есть ли недостаток в использовании этого?
Кевин Куйл,


2
@Kevin Kuyl - Как упоминалось выше Пангом, если массив содержит элемент, который PHP оценивает как ложный (т. Е. 0, "", ноль), этот метод будет иметь неожиданные результаты. Я изменил код, чтобы использовать ===
Эрик Кигати

4
это в основном очень круто, но чтобы прояснить проблему, на которую указывают другие, она неизменно потерпит неудачу с таким массивом, как [true,true,false,true]. Но лично я буду использовать это всякий раз, когда имею дело с массивом, который не содержит логическое значение false.
Биллиноа

4
next()не должны НИКОГДА быть использованы внутри цикла Еогеасп. Это портит внутренний указатель массива. Проверьте документацию для получения дополнительной информации.
Drazzah

89

Более упрощенная версия вышеупомянутого и предполагающая, что вы не используете пользовательские индексы ...

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

Версия 2 - потому что я пришел, чтобы ненавидеть, используя остальное, если не нужно.

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

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

8
Это работает и для объектов. Другие решения работают только для массивов.
Лами

1
Это лучший ответ для меня, но он должен быть сжатым, точка не объявляет длину вне цикла foreach: if ($ index == count ($ array) -1) {...}
Андрей

2
@ И так вы продолжаете считать элементы массива для каждой итерации.
pcarvalho

1
@peteroak Да, на самом деле это технически ухудшит производительность, и в зависимости от того, какой у вас счет или сколько циклов может быть значительным. Так что не обращайте внимания на мой комментарий: D
Андрей

4
@peteroak @Andrew Общее количество элементов в массиве хранится как свойство внутри, поэтому при этом не будет никакого снижения производительности if ($index == count($array) - 1). Смотрите здесь .
GreeKatrina

36

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

Нравится:

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

20

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

$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
Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение того, как работает ваш код и как он решает проблему ОП. Многие СО постеры являются новичками и не поймут код, который вы опубликовали.
Я встревожен иностранцем

4
Этот ответ не говорит, как определить, находитесь ли вы в последней итерации цикла. Это, однако, допустимая попытка ответа и не должна быть помечена как не ответ. Если вам это не нравится, вы должны проголосовать за него, а не отмечать его.
ArtOfWarfare

Понятно, что в первой итерации он вводит первое условие, а затем меняет его значение на false, и таким образом он попадет в первую итерацию только один раз.
Мохамед 23

20

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

// 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).
Кшиштоф Пшигода

2
не должно ли это быть if ($lastkey === $key)?
Кинетический

1
Я получаю:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
Billynoah

1
@Sydwell - прочитайте ошибку. key()получает целое число, а не end(). end()« возвращает значение последнего элемента » и key()ожидает массив в качестве входных данных.
Billynoah


11

1: Почему бы не использовать простое forутверждение? Предполагая, что вы используете реальный массив, а не a, Iteratorвы можете легко проверить, равна ли переменная counter 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: Вы должны рассмотреть возможность использования Nested Sets для хранения вашей древовидной структуры. Кроме того, вы можете улучшить все это с помощью рекурсивных функций.


Если вы собираетесь использовать, forвы можете 1перейти от n-1и к ifS и из тела. Нет смысла проверять их повторно.
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
Это терпит неудачу, когда у вас есть только один элемент в массиве.
Мемочипан

4
Это быстро и легко, если вы можете быть уверены, что в массиве всегда есть более одного элемента (как сказал Memochipan). Так что для меня это не безотказное решение - «лучший ответ».
Seika85

5
Каждый () будет УСТАРЕЛО с PHP 7.2.0 . Пожалуйста, также смотрите php.net/manual/en/function.each.php
поток

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

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

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


6

Для сценариев, генерирующих 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++;

Небольшой домашний тест показал следующее:

test1: 100000 прогонов модели Морг

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

test2: 100000 прогонов модели, если последний

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

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


Ваш код работает только в том случае, если вы можете быть уверены, что у вас есть увеличивающиеся целочисленные ключи. $arr = array('one' => "1 1 1", 4 => 'Four', 1 => 'One'); $numItems = count($arr); $i=0; $firstitem=$arr[0]; echo $i . ': ' . $firstitem . ", "; $i++; while($i<$numItems-1){ $some_item=$arr[$i]; echo $i . ': ' . $some_item . ", "; $i++; } $last_item=$arr[$i]; echo $i . ': ' . $last_item . ", "; $i++;будет выводить:0: , 1: One, 2: ,
Seika85

приведение хеш-карты к массиву - неопределенное поведение, «Объект» array()создан, {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}но пустые элементы игнорируются с помощью count, вы подсчитываете количество определенных «вещей» !! BOUNTY ЖЕРТВЫ ВХОДЯТ! Этот ответ заслуживает награды, но если @Morg. ушел, это бессмысленно. Я бы дал щедрость человеку, который, вероятно, больше не будет использовать SO! Если он вернется и улучшит свой ответ, он заслуживает награды!
mjz19910

Как отмечает @ mjz19910, хэш-карты и массивы не являются взаимозаменяемыми. Однако вы можете получить свойства хеша с помощью array_keysфункции, которую вы можете рассматривать как массив. Смотрите мой "улучшенный" ответ .
TheMadDeveloper

Вот что я использую для запроса:$querySet = ""; foreach ($fieldSet as $key=>$value) { $value = $db->dbLink->quote($value); $querySet .= "$key = $value, "; } $querySet = substr_replace($querySet, "", -2); $queryString = "UPDATE users SET $querySet WHERE user_ID = '$user_ID'";
Ровшан Мамедов

5

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

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

2
Таким образом, вы сравниваете значения и не работаете, если массив содержит два одинаковых элемента.
Кшиштоф Пшигода

5

Использование логической переменной по-прежнему наиболее надежно, даже если вы хотите проверить первое появление $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++;
}

4

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

$firstElement = true;

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

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


2

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

0

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

$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конструкции, опять - таки по ссылке , если это необходимо.

0

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

    $ массив = массив (1,2,3,4);

    $ i = 0;
    $ len = count ($ array);
    foreach (массив $ как $ item) {
        if ($ i === 0) {
            // первый
        } else if ($ i === $ len - 1) {
            // прошлой
        }
        //…
        $ I ++;
    }

0
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();
   }
}

0

Использование reset ($ array) и end ($ array)

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

    }

Демо repl.it


-2

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

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);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.