Come determinare la prima e l'ultima iterazione in un ciclo foreach?


494

La domanda è semplice Ho un foreachciclo nel mio codice:

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

In questo ciclo, voglio reagire diversamente quando siamo nella prima o nell'ultima iterazione.

Come fare questo?

Risposte:


426

È possibile utilizzare un contatore:

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

24
Non penso che il downvoting dovrebbe aver luogo qui perché anche questo funziona correttamente e non è ancora così spazzatura come usare array_shifte array_pop. Anche se questa è la soluzione che avrei trovato se avessi dovuto implementare una cosa del genere, continuerei ora con la risposta del Rok Kralj .
shadyyx,

15
Se ho bisogno di un contatore, preferisco usare il ciclo FOR anziché FOREACH.
rkawano,

10
Se usi $i = 1, non devi preoccuparti $len - 1, basta usare $len.
aksu,

2
@Twan Come va il punto 3 giusto? O pertinente a questa domanda dal momento che si tratta di HTML? Questa è una domanda PHP, chiaramente ... e quando si tratta di mark-up della semantica, si tratta di fatti molto più profondi di "un vero e proprio blahblah è sempre meglio di blahblah (questa non è nemmeno la mia opinione, è un fatto puro)"
Deji,

1
@rkawano ma non puoi ottenere la chiave con nome se usi il ciclo FOR
Fahmi,

1016

Se si preferisce una soluzione che non richiede l'inizializzazione del contatore all'esterno del ciclo, propongo di confrontare la chiave di iterazione corrente con la funzione che indica l'ultimo / primo tasto dell'array.

Questo diventa in qualche modo più efficiente (e più leggibile) con il prossimo PHP 7.3.

Soluzione per PHP 7.3 e versioni successive:

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

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

Soluzione per tutte le versioni di 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
Risposta fantastica! Molto più pulito rispetto all'utilizzo di un sacco di array.
Paul J,

14
Questo dovrebbe arrivare fino in cima perché è la risposta giusta. Un altro vantaggio di queste funzioni rispetto all'uso di array_shift e array_pop è che gli array vengono lasciati intatti, nel caso siano necessari in un secondo momento. +1 per condividere la conoscenza solo per il gusto di farlo.
Awemo,

13
questo è sicuramente il modo migliore se vuoi mantenere pulito il tuo codice. Stavo per votarlo, ma ora non sono convinto che valga la pena il sovraccarico funzionale di quei metodi array. Se stiamo solo parlando dell'ultimo elemento, questo è end()+ key()su ogni iterazione del ciclo - se è entrambi, allora sono 4 i metodi che vengono chiamati ogni volta. Certo, si tratterebbe di operazioni molto leggere e probabilmente sono solo ricerche di puntatori, ma i documenti continuano a specificarlo reset()e a end() modificare il puntatore interno dell'array - quindi è più veloce di un contatore? forse no.
pospi,

19
Non credo che dovresti emettere reset ($ array) all'interno di un foreach. Dalla documentazione ufficiale (www.php.net/foreach): "Poiché foreach si basa sul puntatore di array interno, la modifica all'interno del loop può portare a comportamenti imprevisti." E il reset fa esattamente questo (www.php.net/reset): "Imposta il puntatore interno di un array sul suo primo elemento"
Gonçalo Queirós,

11
@ GonçaloQueirós: funziona. Foreach funziona su una copia dell'array. Tuttavia, se sei ancora preoccupato, sentiti libero di spostare la reset()chiamata prima della ricerca e memorizzare il risultato nella cache $first.
Rok Kralj,

121

Per trovare l'ultimo elemento, trovo che questo pezzo di codice funzioni ogni volta:

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

2
Questo ha troppi voti, c'è qualche svantaggio nell'usarlo?
Kevin Kuyl,


2
@Kevin Kuyl - Come menzionato da Pang sopra, se l'array contiene un elemento che PHP considera falso (ovvero 0, "", null) questo metodo avrà risultati inattesi. Ho modificato il codice per usare ===
Eric Kigathi,

4
questo è per lo più fantastico, ma per chiarire al problema che altri stanno sottolineando, invariabilmente fallirà con un array come [true,true,false,true]. Ma personalmente lo userò ogni volta che ho a che fare con un array che non contiene valori booleani false.
Billynoah,

4
next()non dovrebbe MAI essere usato all'interno di un ciclo foreach. Colpisce il puntatore di array interno. Consulta la documentazione per ulteriori informazioni.
Drazzah,

89

Una versione più semplificata di quanto sopra e presumendo che non si utilizzino indici personalizzati ...

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

Versione 2 - Perché sono venuto a detestare usando l'altro se non necessario.

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

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

8
Questo funziona anche per gli oggetti. Le altre soluzioni funzionano solo per array.
Lamy

1
Questa è la risposta migliore per me, ma dovrebbe essere condensata, nessun punto che dichiara la lunghezza al di fuori del ciclo foreach: if ($ index == count ($ array) -1) {...}
Andrew

2
@Andrew in questo modo continui a contare gli elementi dell'array, per ogni iterazione.
pcarvalho,

1
@peteroak Sì, in realtà danneggerebbe tecnicamente le prestazioni, e in base al conteggio o al numero di loop che potrebbero essere significativi. Quindi ignora il mio commento: D
Andrew,

4
@peteroak @Andrew Il numero totale di elementi in un array viene archiviato come proprietà internamente, quindi non si verificherebbero alcun hit delle prestazioni if ($index == count($array) - 1). Vedi qui .
GreeKatrina,

36

È possibile rimuovere il primo e l'ultimo elemento dall'array ed elaborarli separatamente.

Come questo:

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

Rimuovere tutta la formattazione in CSS anziché i tag incorporati migliorerebbe il tuo codice e accelererebbe i tempi di caricamento.

Potresti anche evitare di mescolare HTML con logica php quando possibile.

La tua pagina potrebbe essere resa molto più leggibile e gestibile separando cose come questa:

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

Un tentativo di trovare il primo sarebbe:

$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
Modifica la tua risposta per aggiungere una spiegazione del funzionamento del codice e di come risolve il problema del PO. Molti poster SO sono neofiti e non capiranno il codice che hai pubblicato.
Ho allarmato l'alieno il

4
Questa risposta non dice come determinare se sei nell'ultima iterazione del ciclo. È, tuttavia, un valido tentativo di risposta, e non dovrebbe essere contrassegnato come non una risposta. Se non ti piace, dovresti votarlo, non segnalarlo.
ArtOfWarfare il

È chiaro, nella prima iterazione entrerà nella prima condizione e poi cambierà il suo valore in falso, e in questo modo entrerà nella prima iterazione solo una volta.
Mohamed23gharbi,

20

Semplicemente funziona!

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

Grazie @billynoah per aver risolto il problema finale .


3
Migliore! Vorrei solo chiarire if ($key === $lastkey).
Krzysztof Przygoda,

2
non dovrebbe essere if ($lastkey === $key)?
Kinetic,

1
Ricevo:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
billynoah, il

1
@Sydwell - leggi l'errore. key()sta ottenendo un numero intero, no end(). end()" restituisce il valore dell'ultimo elemento " e si key()aspetta un array come input.
Billynoah,


11

1: Perché non usare una semplice fordichiarazione? Supponendo che tu stia utilizzando un array reale e non uno, Iteratorpotresti facilmente verificare se la variabile del contatore è 0 o uno in meno dell'intero numero di elementi. Secondo me questa è la soluzione più pulita e comprensibile ...

$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: dovresti considerare l'utilizzo di set nidificati per memorizzare la struttura ad albero. Inoltre, puoi migliorare il tutto utilizzando le funzioni ricorsive.


Se avete intenzione di utilizzare un forpossibile ciclo da 1ad n-1e prendere la ifs fuori dal corpo. Inutile controllarli ripetutamente.
Aprire il

9

Migliore risposta:

$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
Questo non riesce quando si dispone di un solo elemento nell'array.
Memochipan,

4
È facile e veloce, purché tu possa essere sicuro che ci siano sempre più di un lemento nell'array (come diceva Memochipan). Quindi non è una soluzione sicura per me - nessuna "migliore risposta".
Seika85,

5
ognuno () sarà DEPRECATO a partire da PHP 7.2.0 . Vedi anche php.net/manual/en/function.each.php
Flow

8

La risposta più efficiente di @morg, a differenza foreach, funziona solo per array corretti, non per oggetti di mappa hash. Questa risposta evita il sovraccarico di un'istruzione condizionale per ogni iterazione del ciclo, come nella maggior parte di queste risposte (inclusa la risposta accettata) specificatamente gestendo in modo il primo e l'ultimo elemento e eseguendo il ciclo sugli elementi intermedi.

La array_keysfunzione può essere utilizzata per far funzionare la risposta efficiente come 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++;

Non ho fatto benchmarking su questo, ma non è stata aggiunta alcuna logica al loop, che è il più grande successo delle prestazioni, quindi sospetto che i benchmark forniti con la risposta efficiente siano abbastanza vicini.

Se volevi funzionalizzare questo genere di cose, ho preso una svolta a una tale funzione di elenco iterato qui . Tuttavia, potresti voler confrontare il codice gist se sei super preoccupato per l'efficienza. Non sono sicuro di quanto sovraccarico introduca tutta l'invocazione della funzione.


6

Per le query SQL che generano script o tutto ciò che compie un'azione diversa per il primo o l'ultimo elemento, è molto più veloce (quasi due volte più veloce) per evitare l'uso di controlli variabili non necessari.

La soluzione attualmente accettata utilizza un ciclo e un controllo all'interno del ciclo che verrà eseguito every_single_iteration, il modo corretto (veloce) per farlo è il seguente:

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

Un piccolo benchmark fatto in casa ha mostrato quanto segue:

test1: 100000 serie di modelli morg

tempo: 1869.3430423737 millisecondi

test2: 100000 esecuzioni del modello se ultimo

tempo: 3235.6359958649 millisecondi

Ed è quindi abbastanza chiaro che il controllo costa molto, e ovviamente peggiora anche i controlli più variabili che aggiungi;)


Il codice funziona solo se si può essere certi di disporre di chiavi intere incrementali. $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++;uscirà:0: , 1: One, 2: ,
Seika85,

lanciare una mappa hash su un array è un comportamento indefinibile, l '"oggetto" array()creato è {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}ma gli elementi vuoti vengono ignorati con conteggio, si sta contando il numero di "cose" definite !! TAGLIENTE SACRIFICIO IN ARRIVO! Questa risposta merita generosità, ma se @Morg. andato, è inutile. Darei generosità a una persona che probabilmente non userà più SO! Se ritorna e migliora la sua risposta, merita la generosità!
mjz19910,

Come osserva @ mjz19910, le mappe hash e le matrici non sono intercambiabili. Tuttavia, puoi ottenere le proprietà dell'hash con la array_keysfunzione, che puoi trattare come un array. Vedi la mia risposta "migliorata" .
TheMadDeveloper

Questo è ciò che uso per le query:$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

Con chiavi e valori funziona anche:

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

2
In questo modo si confrontano i valori e non funziona se un array contiene due stessi elementi.
Krzysztof Przygoda,

5

L'uso di una variabile booleana è ancora il più affidabile, anche se vuoi controllare la prima apparizione di un $value (l'ho trovato più utile nella mia situazione e in molte situazioni) , come questo:

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

Allora che ne dici !next( $array )di trovare l'ultimo $valueche tornerà truese non c'ènext() valore da iterare.

E preferisco usare un forloop invece che foreachse dovessi usare un contatore, in questo modo:

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

4

Mi sono imbattuto in questa discussione quando ho lo stesso problema. Devo solo ottenere il primo elemento, quindi analizzo nuovamente il mio codice fino a quando non mi viene in mente.

$firstElement = true;

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

I codici sopra sono fantastici e completi ma se hai bisogno solo del primo elemento, puoi provare questo codice.


2

Non sono sicuro se sia ancora necessario. Ma la seguente soluzione dovrebbe funzionare con gli iteratori e non richiede 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

Puoi anche usare una funzione anonima:

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

Altre tre cose da menzionare:

  • Se l'array non è indicizzato rigorosamente (numericamente) è necessario reindirizzare l'array array_values .
  • Se è necessario modificarlo, $elementè necessario passarlo per riferimento (&$element ).
  • Tutte le variabili esterne alla funzione anonima di cui hai bisogno all'interno, dovrai elencarle accanto $indexOfLastElementall'interno del usecostrutto, di nuovo per riferimento se necessario.

0

È possibile utilizzare il contatore e la lunghezza dell'array.

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

    $ i = 0;
    $ len = count ($ array);
    foreach ($ array come $ item) {
        if ($ i === 0) {
            // primo
        } altrimenti if ($ i === $ len - 1) {
            // scorso
        }
        // ...
        $ 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

Utilizzo di reset ($ array) e 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


-2

Prova questo:

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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.