Як визначити першу та останню ітерацію в циклі 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
@Twan Як правильно відповідає точка 3? Або взагалі стосується цього питання, оскільки воно включає HTML? Це питання PHP, зрозуміло ... і якщо мова йде про семантику розмітки, то це зводиться до набагато глибших фактів, ніж "власне благбла завжди краще, ніж блабла (це навіть не моя думка, це чистий факт)".
— Deji

1
@rkawano, але ви не можете отримати названий ключ, якщо ви використовуєте цикл FOR
— Fahmi

1014

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

Це стає дещо ефективнішим (і читабельніше) з майбутнім 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
Фантастична відповідь! Набагато чистіше, ніж використовувати купу масивів.
— Пол J

14
Це повинно бути бульбашкою аж до вершини, тому що це правильна відповідь. Ще одна перевага цих функцій перед використанням array_shift та array_pop полягає в тому, що масиви залишаються неушкодженими, на випадок, якщо вони знадобляться в подальшому. +1 для обміну знаннями просто заради цього.
— Awemo

13
це, безумовно, найкращий спосіб, якщо ви хочете зберегти чистий код. Я вже збирався його схвалити, але зараз я не впевнений, що функціональні витрати цих методів масиву того варті. Якщо ми просто говоримо про останній елемент, то це end()+ key()над кожною ітерацією циклу - якщо це обидва, то кожен раз викликають 4 методи. Зрозуміло, це були б дуже легкі операції і, ймовірно, є лише пошуковими вказівниками, але потім документи продовжують вказувати це reset()та end() змінювати внутрішній покажчик масиву - так це швидше, ніж лічильник? можливо, ні.
— pospi

19
Я не думаю, що вам слід видавати скидання ($ масив) всередині foreach. З офіційної документації (www.php.net/foreach): "Оскільки Foreach покладається на внутрішній покажчик масиву, зміна його в циклі може призвести до несподіваної поведінки". І скидання робить саме це (www.php.net/reset): "Встановіть внутрішній вказівник масиву на його перший елемент"
— Gonçalo Queirós

11
@ GonçaloQueirós: Це працює. Foreach працює над копією масиву. Однак якщо ви все ще стурбовані, не соромтеся перенести reset()виклик перед програмою foreach та кешувати результат $first.
— Rok Kralj

121

Щоб знайти останній елемент, я вважаю, що цей фрагмент коду працює щоразу:

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

2
У цьому занадто мало результатів, чи є недолік використання цього?
— Кевін Куйл


2
@Kevin Kuyl - Як згадував Панг вище, якщо масив містить елемент, який PHP оцінює як хибний (тобто 0, "", null), цей метод матиме несподівані результати. Я змінив код, щоб використовувати ===
— Ерік Кігаті

4
Це, як правило, дуже дивовижно, але якщо з'ясувати, на що проблеми вказують інші, воно незмінно вийде з масиву на зразок [true,true,false,true]. Але особисто я буду використовувати це в будь-який час, коли я маю справу з масивом, який не містить булевих false.
— billynoah

4
next()не повинні НІКОЛИ бути використані всередині циклу Еогеасп. Він псує внутрішній покажчик масиву. Перегляньте документацію для отримання додаткової інформації.
— Дража

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 ($ масив) -1) {...}
— Андрій

2
@Аndrew таким чином ви продовжуєте рахувати елементи масиву для кожної ітерації.
— 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

Зрозуміло, що під час першої ітерації він введе першу умову, а потім змінить її значення на помилкове, і таким чином він потрапить у першу ітерацію лише один раз.
— Mohamed23gharbi

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).
— Krzysztof Przygoda

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твердження? Якщо припустити, що ви використовуєте реальний масив, а не 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: Вам слід розглянути можливість використання вкладених наборів для зберігання вашої структури дерева. Крім того, ви можете покращити все, використовуючи рекурсивні функції.


Якщо ви збираєтесь використовувати a, 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
Це швидко і легко, якщо ви можете бути впевнені, що в масиві завжди є більше одного лементу (як сказав Мемочіпан). Тож для мене це небезпечне рішення - немає «найкращої відповіді».
— 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"}але порожні елементи ігноруються підрахунком, ви рахуєте кількість визначених "речей" !! СВЯТИ СВЯТИ БУНТИ! Ця відповідь заслуговує на винагороду, але якщо @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
Таким чином ви порівнюєте значення і не працює, якщо масив містить два однакових елемента.
— Krzysztof Przygoda

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 ($ масив);
    foreach ($ масив як $ item) {
        якщо ($ 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

Використання скидання ($ масив) та закінчення ($ масив)

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

    }

Демонстраційний репл.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.