Comment déterminer la première et la dernière itération dans une boucle foreach?

494

La question est simple. J'ai une foreachboucle dans mon code:

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

Dans cette boucle, je veux réagir différemment lorsque nous sommes dans la première ou la dernière itération.

Comment faire ça?

mehdi
la source

Réponses:

426

Vous pouvez utiliser un compteur:

$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}
Gombo
la source
24
Je ne pense pas que le downvoting devrait avoir lieu ici car cela fonctionne également correctement et n'est toujours pas aussi nul que d'utiliser array_shiftet array_pop. Bien que ce soit la solution que j'aurais trouvée si je devais mettre en œuvre une telle chose, je m'en tiendrai à la réponse de Rok Kralj maintenant.
shadyyx
15
Si j'ai besoin d'un compteur, je préfère utiliser la boucle FOR au lieu de FOREACH.
rkawano
10
Si vous utilisez $i = 1, vous n'avez pas à vous en soucier $len - 1, utilisez simplement $len.
aksu
2
@Twan Comment le point # 3 est-il correct? Ou tout à fait pertinent à cette question car il s'agit de HTML? C'est une question PHP, clairement ... et quand il s'agit de balisage sémantique, c'est à des faits beaucoup plus profonds que "une bonne blahblah est toujours meilleure que blahblah (ce n'est même pas mon avis, c'est un pur fait)"
Deji
1
@rkawano mais vous ne pouvez pas obtenir la clé nommée si vous utilisez la boucle FOR
Fahmi
1016

Si vous préférez une solution qui ne nécessite pas l'initialisation du compteur en dehors de la boucle, je vous propose de comparer la clé d'itération actuelle à la fonction qui vous indique la dernière / première clé du tableau.

Cela devient un peu plus efficace (et plus lisible) avec le prochain PHP 7.3.

Solution pour PHP 7.3 et supérieur:

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

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

Solution pour toutes les versions 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
la source
44
Une réponse fantastique! Beaucoup plus propre que l'utilisation d'un tas de tableaux.
Paul J
14
Cela devrait remonter jusqu'en haut, car c'est la bonne réponse. Un autre avantage de ces fonctions par rapport à l'utilisation de array_shift et array_pop est que les tableaux sont laissés intacts, au cas où ils seraient nécessaires ultérieurement. +1 pour partager des connaissances juste pour le plaisir.
Awemo
13
c'est certainement la meilleure façon si vous voulez garder votre code propre. J'étais sur le point de voter, mais maintenant je ne suis pas convaincu que la surcharge fonctionnelle de ces méthodes de tableau en vaille la peine. Si nous ne parlons que du dernier élément, alors c'est end()+ key()à chaque itération de la boucle - si c'est les deux, c'est que 4 méthodes sont appelées à chaque fois. Certes, ce seraient des opérations très légères et ne sont probablement que des recherches de pointeur, mais les documents continuent de le spécifier reset()et de end() modifier le pointeur interne du tableau - est-ce donc plus rapide qu'un compteur? peut-être pas.
pospi
19
Je ne pense pas que vous devriez émettre un reset ($ array) dans un foreach. D'après la documentation officielle (www.php.net/foreach): "Comme foreach repose sur le pointeur de tableau interne, le changer dans la boucle peut conduire à un comportement inattendu." Et réinitialiser fait exactement cela (www.php.net/reset): "Réglez le pointeur interne d'un tableau sur son premier élément"
Gonçalo Queirós
11
@ GonçaloQueirós: Cela fonctionne. Foreach fonctionne sur une copie du tableau. Cependant, si vous êtes toujours concerné, n'hésitez pas à déplacer l' reset()appel avant le foreach et à mettre le résultat en cache $first.
Rok Kralj
121

Pour trouver le dernier élément, je trouve que ce morceau de code fonctionne à chaque fois:

foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}
Yojance
la source
2
Cela a trop peu de votes positifs, y a-t-il un inconvénient à l'utiliser?
Kevin Kuyl
2
@Kevin Kuyl - Comme mentionné par Pang ci-dessus, si le tableau contient un élément que PHP évalue comme faux (c'est-à-dire 0, "", null) cette méthode aura des résultats inattendus. J'ai modifié le code pour utiliser ===
Eric Kigathi
4
c'est surtout très impressionnant mais pour clarifier le problème, d'autres soulignent que cela échouera invariablement avec un tableau comme [true,true,false,true]. Mais personnellement, je l'utiliserai chaque fois que je traiterai avec un tableau qui ne contient pas de booléen false.
billynoah
4
next()ne doit JAMAIS être utilisé dans une boucle foreach. Il gâche le pointeur de tableau interne. Consultez la documentation pour plus d'informations.
Drazzah
89

Une version plus simplifiée de ce qui précède et en supposant que vous n'utilisez pas d'index personnalisés ...

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

Version 2 - Parce que j'en suis venu à détester utiliser le else sauf si nécessaire.

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

    if ($index == $len - 1) {
        // last
        // do something
        continue;
    }
}
Hayden
la source
8
Cela fonctionne aussi pour les objets. Les autres solutions ne fonctionnent que pour les tableaux.
Lamy
1
C'est la meilleure réponse pour moi, mais devrait être condensée, aucun point ne déclarant la longueur en dehors de la boucle foreach: if ($ index == count ($ array) -1) {...}
Andrew
2
@Andrew de cette façon, vous continuez à compter les éléments du tableau, pour chaque itération.
pcarvalho
1
@peteroak Oui, en fait, cela nuirait techniquement aux performances, et selon ce que votre comptage ou le nombre de boucles pourrait être significatif. Alors ne tenez pas compte de mon commentaire: D
Andrew
4
@peteroak @Andrew Le nombre total d'éléments dans un tableau est stocké en tant que propriété en interne, donc il n'y aurait pas de résultats de performance en faisant if ($index == count($array) - 1). Voyez ici .
GreeKatrina
36

Vous pouvez supprimer les premier et dernier éléments du tableau et les traiter séparément.

Comme ça:

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

La suppression de toute la mise en forme en CSS au lieu des balises en ligne améliorerait votre code et accélérerait le temps de chargement.

Vous pouvez également éviter de mélanger le HTML avec la logique php autant que possible.

Votre page pourrait être rendue beaucoup plus lisible et maintenable en séparant des choses comme ceci:

<?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
la source
20

Une tentative pour trouver le premier serait:

$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
la source
3
Veuillez modifier votre réponse pour ajouter une explication sur le fonctionnement de votre code et comment il résout le problème de l'OP. De nombreuses affiches SO sont des débutants et ne comprendront pas le code que vous avez publié.
J'ai alarmé un extraterrestre
4
Cette réponse ne dit pas comment déterminer si vous êtes dans la dernière itération de la boucle. Il s'agit cependant d'une tentative valable de réponse et ne doit pas être signalée comme non-réponse. Si vous ne l'aimez pas, vous devez voter contre, pas le signaler.
ArtOfWarfare
C'est clair, dans la première itération, il entrera dans la première condition et changera ensuite sa valeur en false, et de cette façon, il n'entrera dans la première itération qu'une seule fois.
Mohamed23gharbi
20

Cela fonctionne simplement!

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

Merci @billynoah pour avoir trié le problème final .

Sydwell
la source
3
Meilleur! Je voudrais seulement clarifier if ($key === $lastkey).
Krzysztof Przygoda
2
ne devrait-il pas en être ainsi if ($lastkey === $key)?
Kinetic
1
PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
J'obtiens
1
@Sydwell - lisez l'erreur. key()obtient un entier, non end(). end()" renvoie la valeur du dernier élément " et key()attend un tableau en entrée.
billynoah
11

1: Pourquoi ne pas utiliser une simple fordéclaration? En supposant que vous utilisez un vrai tableau et non un tableau, Iteratorvous pouvez facilement vérifier si la variable de compteur est 0 ou un de moins que le nombre entier d'éléments. À mon avis, c'est la solution la plus propre et la plus compréhensible ...

$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: Vous devriez envisager d'utiliser des ensembles imbriqués pour stocker votre structure arborescente. De plus, vous pouvez améliorer le tout en utilisant des fonctions récursives.

okoman
la source
Si vous allez utiliser un forvous pouvez faire une boucle à partir 1de n-1et prendre la ifs hors du corps. Inutile de les vérifier à plusieurs reprises.
mpen
9

Meilleure réponse:

$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
la source
2
Cela échoue lorsque vous n'avez qu'un seul élément dans le tableau.
Memochipan
4
C'est rapide et facile tant que vous pouvez être sûr qu'il y a toujours plus d'un élément dans le tableau (comme l'a dit Memochipan). Ce n'est donc pas une solution de sécurité pour moi - pas de «meilleure réponse».
Seika85
5
each () sera obsolète à partir de PHP 7.2.0 . Veuillez également consulter php.net/manual/en/function.each.php
Flow
8

La réponse la plus efficace de @morg, contrairement à foreach, ne fonctionne que pour les tableaux appropriés, pas les objets de carte de hachage. Cette réponse évite la surcharge d'une instruction conditionnelle pour chaque itération de la boucle, comme dans la plupart de ces réponses (y compris la réponse acceptée) en traitant spécifiquement le premier et le dernier élément, et en bouclant sur les éléments du milieu.

La array_keysfonction peut être utilisée pour faire fonctionner la réponse efficace comme 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++;

Je n'ai pas fait de benchmarking à ce sujet, mais aucune logique n'a été ajoutée à la boucle, ce qui est le plus grand coup à la performance, alors je soupçonne que les benchmarks fournis avec la réponse efficace sont assez proches.

Si vous vouliez fonctionnaliser ce genre de chose, j'ai essayé une telle fonction iterateList ici . Cependant, vous voudrez peut-être comparer le code gist si vous êtes très préoccupé par l'efficacité. Je ne sais pas combien de frais généraux toute l'invocation de fonction introduit.

TheMadDeveloper
la source
6

Pour les scripts générant des requêtes SQL, ou tout ce qui fait une action différente pour le premier ou le dernier élément, il est beaucoup plus rapide (presque deux fois plus rapide) d'éviter d'utiliser des vérifications de variables inutiles.

La solution actuellement acceptée utilise une boucle et une vérification dans la boucle qui sera effectuée à chaque_single_iteration, la bonne (rapide) façon de procéder est la suivante:

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

Un petit repère fait maison a montré ce qui suit:

test1: 100000 exécutions de modèle morg

temps: 1869.3430423737 millisecondes

test2: 100000 exécutions du modèle si dernier

temps: 3235.6359958649 millisecondes

Et il est donc tout à fait clair que le chèque coûte cher, et bien sûr, il devient encore pire avec les chèques plus variables que vous ajoutez;)

Morg.
la source
Votre code ne fonctionne que si vous pouvez être sûr d'avoir des clés entières incrémentielles. $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++;affichera:0: , 1: One, 2: ,
Seika85
lancer une carte de hachage dans un tableau est un comportement indéfinissable, le "Object" array()fait est {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}mais les éléments vides sont ignorés avec count, vous comptez le nombre de "choses" définies !! SACRIFICE BOUNTY ENTRANT! Cette réponse mérite des primes, mais si @Morg. disparu, c'est inutile. Je donnerais une prime à une personne qui n'utilisera probablement plus SO! S'il revient et améliore sa réponse, il mérite la prime!
mjz19910
Comme le note @ mjz19910, les cartes de hachage et les tableaux ne sont pas interchangeables. Cependant, vous pouvez obtenir les propriétés du hachage avec la array_keysfonction, que vous pouvez traiter comme un tableau. Voir ma réponse "améliorée" .
TheMadDeveloper
C'est ce que j'utilise pour la requête:$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

Avec les clés et les valeurs, cela fonctionne également:

foreach ($array as $key => $value) {
    if ($value === end($array)) {
        echo "LAST ELEMENT!";
    }
}
Benibr
la source
2
De cette façon, vous comparez des valeurs et ne fonctionne pas si un tableau contient deux mêmes éléments.
Krzysztof Przygoda
5

L'utilisation d'une variable booléenne est toujours la plus fiable, même si vous voulez vérifier la première apparition d'un $value (je l'ai trouvé plus utile dans ma situation et dans de nombreuses situations) , comme ceci:

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

Alors que diriez-vous !next( $array )de trouver le dernier $valuequi reviendra trues'il n'y a pas de next()valeur à itérer.

Et je préfère utiliser une forboucle plutôt que foreachsi j'allais utiliser un compteur, comme ceci:

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

Je suis tombé sur ce fil quand j'ai le même problème. Je n'ai besoin que du premier élément, puis je ré-analyse mon code jusqu'à ce que cela me vienne à l'esprit.

$firstElement = true;

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

Les codes ci-dessus sont excellents et complets, mais si vous n'avez besoin que du premier élément, vous pouvez essayer ce code.

Paul
la source
2

Je ne sais pas si c'est encore nécessaire. Mais la solution suivante devrait fonctionner avec des itérateurs et ne nécessite pas 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
la source
0

Vous pouvez également utiliser une fonction anonyme:

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

Trois autres choses doivent être mentionnées:

  • Si votre tableau n'est pas indexé strictement (numériquement), vous devez d'abord diriger votre tableau array_values.
  • Si vous devez modifier le, $elementvous devez le passer par reference ( &$element).
  • Toutes les variables extérieures à la fonction anonyme dont vous avez besoin à l'intérieur, vous devrez les répertorier à côté de l' $indexOfLastElementintérieur de la useconstruction, à nouveau par référence si nécessaire.
undko
la source
0

Vous pouvez utiliser le compteur et la longueur du tableau.

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

    $ i = 0;
    $ len = count ($ array);
    foreach ($ array as $ item) {
        si ($ i === 0) {
            // première
        } else if ($ i === $ len - 1) {
            // dernier
        }
        //…
        $ i ++;
    }
Jesus Erwin Suarez
la source
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
la source
0

Utilisation de reset ($ array) et 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

antelove
la source
-2

Essaye ça:

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
la source