Jak ustalić pierwszą i ostatnią iterację w pętli foreach?

494

Pytanie jest proste. Mam foreachpętlę w kodzie:

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

W tej pętli chcę inaczej reagować, gdy jesteśmy w pierwszej lub ostatniej iteracji.

Jak to zrobić?

mehdi
źródło

Odpowiedzi:

426

Możesz użyć licznika:

$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}
Gumbo
źródło
24
Nie sądzę, że głosowanie w dół powinno odbywać się tutaj, ponieważ to również działa poprawnie i nadal nie jest tak banalne jak używanie array_shifti array_pop. Chociaż to jest rozwiązanie, które wymyśliłem, gdybym musiał wdrożyć coś takiego, trzymałbym się teraz odpowiedzi Rok Kralja .
shadyyx
15
Jeśli potrzebuję licznika, wolę użyć pętli FOR zamiast FOREACH.
rkawano
10
Jeśli używasz $i = 1, nie musisz się martwić $len - 1, po prostu użyj $len.
aksu,
2
@Twan Jak prawidłowy jest punkt 3? Czy w ogóle ma znaczenie dla tego pytania, ponieważ dotyczy HTML? To jest pytanie PHP, oczywiście ... a jeśli chodzi o semantykę marży, sprowadza się to do znacznie głębszych faktów niż „właściwa blablah jest zawsze lepsza niż blablah (to nawet nie moja opinia, to czysty fakt)”
Deji,
1
@rkawano, ale nie możesz uzyskać nazwanego klucza, jeśli używasz pętli FOR
Fahmi
1014

Jeśli wolisz rozwiązanie, które nie wymaga inicjalizacji licznika poza pętlą, proponuję porównanie bieżącego klucza iteracji z funkcją, która mówi ci ostatni / pierwszy klucz tablicy.

Staje się to nieco bardziej wydajne (i bardziej czytelne) w nadchodzącym PHP 7.3.

Rozwiązanie dla PHP 7.3 i nowszych:

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

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

Rozwiązanie dla wszystkich wersji PHP:

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

    end($array);
    if ($key === key($array))
        echo 'LAST ELEMENT!';
}
Rok Kralj
źródło
44
Fantastyczna odpowiedź! Znacznie czystsze niż używanie zestawu tablic.
Paul J
14
Powinno to znaleźć się na samej górze, ponieważ jest to właściwa odpowiedź. Kolejną zaletą tych funkcji w porównaniu z użyciem array_shift i array_pop jest to, że tablice pozostają nienaruszone, na wypadek, gdyby były potrzebne później. +1 za dzielenie się wiedzą tylko ze względu na nią.
Awemo,
13
jest to zdecydowanie najlepszy sposób, jeśli chcesz utrzymać swój kod w czystości. Miałem zamiar go zagłosować, ale teraz nie jestem przekonany, że narzut funkcjonalny tych metod tablicowych jest tego wart. Jeśli mówimy tylko o ostatnim elemencie, oznacza to, że end()+ key()na każdej iteracji pętli - jeśli to obie, to za każdym razem wywoływane są 4 metody. To prawda, że ​​byłyby to bardzo lekkie operacje i prawdopodobnie są to po prostu wyszukiwanie wskaźników, ale potem dokumenty określają to reset()i end() modyfikują wewnętrzny wskaźnik tablicy - więc czy jest szybszy niż licznik? być może nie.
pospi
19
Nie sądzę, że powinieneś wydać reset (tablica $) w foreach. Z oficjalnej dokumentacji (www.php.net/foreach): „Ponieważ foreach opiera się na wskaźniku tablicy wewnętrznej, zmiana go w pętli może prowadzić do nieoczekiwanego zachowania”. Zresetowanie robi dokładnie to (www.php.net/reset): „Ustaw wewnętrzny wskaźnik tablicy na jej pierwszy element”
Gonçalo Queirós,
11
@ GonçaloQueirós: To działa. Foreach działa na kopii tablicy. Jeśli jednak nadal jesteś zaniepokojony, możesz przenieść reset()rozmowę przed foreach i zapisać wynik w pamięci podręcznej $first.
Rok Kralj
121

Aby znaleźć ostatni element, uważam, że ten fragment kodu działa za każdym razem:

foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}
Yojance
źródło
2
Ma to za mało głosów poparcia, czy korzystanie z tego ma jakąś wadę?
Kevin Kuyl
2
@Kevin Kuyl - Jak wspomniano powyżej Pang, jeśli tablica zawiera element, który PHP ocenia jako fałszywy (tj. 0, „”, null), ta metoda będzie miała nieoczekiwane wyniki. Zmodyfikowałem kod, aby używał ===
Eric Kigathi
4
jest to w większości bardzo niesamowite, ale aby wyjaśnić problem, na który wskazują inni, niezmiennie nie powiedzie się z tablicą podobną do [true,true,false,true]. Ale osobiście będę tego używał za każdym razem, gdy mam do czynienia z tablicą, która nie zawiera wartości logicznej false.
billynoah
4
next()NIGDY nie powinien być używany wewnątrz pętli foreach. Zmiesza wskaźnik wewnętrzny tablicy. Sprawdź dokumentację, aby uzyskać więcej informacji.
Drazzah
89

Bardziej uproszczona wersja powyższego i zakładając, że nie używasz niestandardowych indeksów ...

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

Wersja 2 - Ponieważ nienawidzę używać innych, chyba że jest to konieczne.

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

    if ($index == $len - 1) {
        // last
        // do something
        continue;
    }
}
Hayden
źródło
8
Działa to również w przypadku obiektów. Inne rozwiązania działają tylko dla tablic.
Lamy
1
To dla mnie najlepsza odpowiedź, ale należy ją streścić, bez sensu deklarowania długości poza pętlą foreach: if ($ index == count ($ array) -1) {...}
Andrew
2
@Andrew w ten sposób ciągle liczysz elementy tablicy dla każdej iteracji.
pcarvalho
1
@peteroak Tak, tak naprawdę technicznie wpłynęłoby to negatywnie na wydajność, w zależności od tego, ile zliczasz lub ile pętli może być znaczących. Więc zignoruj ​​mój komentarz: D
Andrew,
4
@peteroak @Andrew Całkowita liczba elementów w tablicy jest przechowywana wewnętrznie jako właściwość, więc nie spowoduje to żadnego spadku wydajności if ($index == count($array) - 1). Zobacz tutaj .
GreeKatrina
36

Możesz usunąć pierwszy i ostatni element z tablicy i przetwarzać je osobno.

Lubię to:

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

Usunięcie całego formatowania do CSS zamiast tagów wbudowanych poprawiłoby kod i przyspieszyło ładowanie.

W miarę możliwości można także unikać mieszania HTML z logiką php.

Twoja strona może stać się o wiele bardziej czytelna i łatwa w utrzymaniu, dzieląc takie rzeczy:

<?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
}
?>
Carlos Lima
źródło
20

Próbą znalezienia pierwszego byłoby:

$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
źródło
3
Edytuj swoją odpowiedź, aby dodać wyjaśnienie, w jaki sposób działa Twój kod i jak rozwiązuje problem PO. Wiele plakatów SO to nowicjusze i nie zrozumieją opublikowanego kodu.
zaalarmowałem kosmitę
4
Ta odpowiedź nie mówi, jak ustalić, czy jesteś w ostatniej iteracji pętli. Jest to jednak poprawna próba odpowiedzi i nie należy oznaczać jej jako odpowiedzi. Jeśli ci się nie podoba, powinieneś głosować, a nie oznaczać.
ArtOfWarfare
Jest jasne, że w pierwszej iteracji wejdzie w pierwszy warunek, a następnie zmieni wartość na false, a tym samym przejdzie do pierwszej iteracji tylko raz.
Mohamed23gharbi,
20

Po prostu to działa!

// 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];
}

Dziękuję @billynoah za rozwiązanie problemu końcowego .

Sydwell
źródło
3
Najlepsza! Chciałbym tylko wyjaśnić if ($key === $lastkey).
Krzysztof Przygoda
2
nie powinno tak być if ($lastkey === $key)?
Kinetic
1
Dostaję:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
billynoah
1
@Sydwell - przeczytaj błąd. key()otrzymuje liczbę całkowitą, nie end(). end()zwraca wartość ostatniego elementu ” i key()oczekuje tablicy jako danych wejściowych.
billynoah
11

1: Dlaczego nie użyć prostego forstwierdzenia? Zakładając, że używasz prawdziwej tablicy, a nie an, Iteratormożesz łatwo sprawdzić, czy zmienna licznika jest równa 0, czy jeden mniejsza niż cała liczba elementów. Moim zdaniem jest to najbardziej czyste i zrozumiałe rozwiązanie ...

$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: Należy rozważyć użycie zestawów zagnieżdżonych do przechowywania struktury drzewa. Dodatkowo możesz ulepszyć całość, korzystając z funkcji rekurencyjnych.

okoman
źródło
Jeśli masz zamiar użyć formożna od pętli 1do n-1i wziąć ifs poza ciałem. Nie ma sensu sprawdzać ich wielokrotnie.
mpen 28.01.16
9

Najlepsza odpowiedź:

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

}
Ivan
źródło
2
To się nie udaje, gdy masz tylko jeden element w tablicy.
Memochipan
4
Jest to szybkie i łatwe, o ile masz pewność, że w tablicy zawsze znajduje się więcej niż jeden lement (jak powiedział Memochipan). Nie jest to więc dla mnie niezawodne rozwiązanie - brak „najlepszej odpowiedzi”.
Seika85
5
each () będzie DEPRECATED od PHP 7.2.0 . Zobacz także php.net/manual/en/function.each.php
Flow
8

Najbardziej wydajna odpowiedź z @morg, w przeciwieństwie do foreach, działa tylko dla odpowiednich tablic, a nie dla obiektów mapy mieszania. Ta odpowiedź pozwala uniknąć narzutu instrukcji warunkowej dla każdej iteracji pętli, tak jak w większości tych odpowiedzi (w tym odpowiedzi zaakceptowanej) poprzez specyficzną obsługę pierwszego i ostatniego elementu oraz zapętlanie elementów środkowych.

array_keysFunkcja może być użyta do dokonania efektywnej pracy odpowiedzi jak 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++;

Nie przeprowadziłem testów porównawczych w tym zakresie, ale do pętli nie dodano żadnej logiki, która jest największym hitem wydajności, więc podejrzewam, że testy porównawcze z wydajną odpowiedzią są dość zbliżone.

Jeśli chcesz funkcjonalizować tego rodzaju rzeczy, podważyłem tutaj funkcję iterateList . Możesz jednak chcieć przetestować kod GIST, jeśli bardzo martwisz się wydajnością. Nie jestem pewien, ile narzucają wszystkie wywołania funkcji.

TheMadDeveloper
źródło
6

W przypadku skryptów generujących zapytania SQL lub czegokolwiek, co wykonuje inną akcję dla pierwszego lub ostatniego elementu, jest to znacznie szybsze (prawie dwa razy szybsze), aby uniknąć niepotrzebnego sprawdzania zmiennych.

Obecnie akceptowane rozwiązanie wykorzystuje pętlę i sprawdzanie w pętli, które będzie wykonywane co każde pojedyncze sformułowanie, poprawny (szybki) sposób to zrobić:

$numItems = count($arr);
$i=0;
$firstitem=$arr[0];
$i++;
while($i<$numItems-1){
    $some_item=$arr[$i];
    $i++;
}
$last_item=$arr[$i];
$i++;

Mały domowy test porównawczy pokazał, co następuje:

test1: 100000 serii modelu Morg

czas: 1869.3430423737 milisekund

test2: 100000 serii modelu, jeśli ostatnia

czas: 3235.6359958649 milisekund

I dlatego jest całkiem jasne, że czek kosztuje dużo i oczywiście staje się jeszcze gorzej, gdy dodajesz więcej zmiennych czeków;)

Morg.
źródło
Twój kod działa tylko wtedy, gdy możesz mieć pewność, że będziesz mieć przyrostowe klucze całkowite. $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++;wyświetli:0: , 1: One, 2: ,
Seika85
rzutowanie mapy skrótu na tablicę jest nieokreślonym zachowaniem, wykonane „Obiekt” array()jest, {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}ale puste elementy są ignorowane przy pomocy count, liczy się liczba zdefiniowanych „rzeczy” !! BOUNTY OFIARY POCHODZĄCE! Ta odpowiedź zasługuje na nagrodę, ale jeśli @Morg. odszedł, to nie ma sensu. Dałbym nagrodę osobie, która prawdopodobnie nie użyje SO ponownie! Jeśli wróci i poprawi odpowiedź, zasługuje na nagrodę!
mjz19910
Jak zauważa @ mjz19910, mapy skrótów i tablice nie są wymienne. Można jednak uzyskać właściwości skrótu za pomocą array_keysfunkcji, którą można traktować jak tablicę. Zobacz moją „ulepszoną” odpowiedź .
TheMadDeveloper
Właśnie tego używam do zapytania:$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'";
Rovshan Mamedov
5

W przypadku kluczy i wartości działa to również:

foreach ($array as $key => $value) {
    if ($value === end($array)) {
        echo "LAST ELEMENT!";
    }
}
Benibr
źródło
2
W ten sposób porównujesz wartości i nie działa, jeśli tablica zawiera dwa takie same elementy.
Krzysztof Przygoda
5

Korzystanie ze zmiennej boolowskiej jest nadal najbardziej niezawodne, nawet jeśli chcesz sprawdzić pierwsze pojawienie się zmiennej $value (uznałem ją za bardziej przydatną w mojej sytuacji i w wielu sytuacjach) , na przykład:

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

Więc jak !next( $array )znaleźć ostatni, $valuektóry zwróci, truejeśli nie ma next()wartości do iteracji.

I wolę użyć forpętli, niż foreachgdybym miał użyć licznika, takiego jak to:

$len = count( $array );
for ( $i = 0; $i < $len; $i++ ) {
    $value = $array[$i];
    if ($i === 0) {
        // first
    } elseif ( $i === $len - 1 ) {
        // last
    }
    // …
    $i++;
}
5ervant
źródło
4

Natknąłem się na ten wątek, gdy mam ten sam problem. Muszę tylko zdobyć pierwszy element, a następnie ponownie przeanalizować kod, dopóki nie przyszło mi to do głowy.

$firstElement = true;

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

Powyższe kody są świetne i kompletne, ale jeśli potrzebujesz tylko pierwszego elementu, możesz wypróbować ten kod.

Paweł
źródło
2

Nie jestem pewien, czy nadal jest to konieczne. Ale poniższe rozwiązanie powinno współpracować z iteratorami i nie wymaga 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
źródło
0

Możesz także użyć funkcji anonimowej:

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

Należy wymienić jeszcze trzy rzeczy:

  • Jeśli twoja tablica nie jest ściśle indeksowana (liczbowo), najpierw musisz ją przepuścić array_values.
  • Jeśli musisz zmodyfikować $element, musisz przekazać go przez referencję ( &$element).
  • Wszelkie zmiennych spoza funkcji anonimowej, czego potrzeba do środka, trzeba będzie je wymienić obok $indexOfLastElementwewnątrz usekonstrukcji, przez odniesienie ponownie w razie potrzeby.
undko
źródło
0

Możesz użyć licznika i długości tablicy.

    $ tablica = tablica (1,2,3,4);

    $ i = 0;
    $ len = liczba ($ tablica);
    foreach ($ tablica jako $ element) {
        jeśli ($ i === 0) {
            // pierwszy
        } else if ($ i === $ len - 1) {
            // ostatni, ubiegły, zeszły
        }
        //…
        $ i ++;
    }
Jesus Erwin Suarez
źródło
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();
   }
}
Joao Paulo Pinheiro
źródło
0

Korzystanie z resetowania ($ array) i 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>";
        }                

    }

Demo repl.it

antylopa
źródło
-2

Spróbuj tego:

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
źródło