Как определить первую и последнюю итерацию в цикле 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';
    }
}
Yojance
источник
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;
    }
}
Hayden
источник
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
  }
}
sstauross
источник
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, за то, что вы решили проблему в конце .

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

okoman
источник
Если вы собираетесь использовать, 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 . Хотя, возможно, вы захотите сравнить основной код, если вы очень заинтересованы в эффективности. Я не уверен, сколько накладных расходов вносит все вызовы функций.

TheMadDeveloper
источник
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!";
    }
}
Benibr
источник
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++;
}
5ervant
источник
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;
        }
    }
}
vbarbarosh
источник
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конструкции, опять - таки по ссылке , если это необходимо.
undko
источник
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

antelove
источник
-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);
PureField
источник