web-dev-qa-db-fra.com

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

La question est simple. J'ai une boucle foreach 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?

428
mehdi

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++;
}
367
Gumbo

Si vous préférez une solution qui ne nécessite pas l'initialisation du compteur en dehors de la boucle, je 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 la prochaine version PHP 7.3.

Solution pour PHP 7.3 et versions ultérieures:

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!';
}
890
Rok Kralj

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

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

Une version plus simplifiée de ce qui précède et 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 je suis venu à détester en utilisant le reste, 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;
    }
}
73
Hayden

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

Supprimer toute la mise en forme en CSS au lieu des balises intégrées améliorerait votre code et accélérerait le temps de chargement.

Vous pouvez également éviter de mélanger 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) {
  //retirive 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
}
?>
33
Carlos Lima

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
  }
}
14
sstauross

Tout simplement cela fonctionne!

// 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 réglé le problème end .

14
Sydwell

1: Pourquoi ne pas utiliser une simple instruction for? En supposant que vous utilisez un tableau réel et non une Iterator, vous pouvez facilement vérifier si la variable counter est 0 ou un moins que le nombre total d'éléments. A 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 Ensembles imbriqués pour stocker votre arborescence. De plus, vous pouvez améliorer le tout en utilisant des fonctions récursives.

10
okoman

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

}
8
Ivan

Le plus réponse efficace } de @morg, contrairement à foreach, ne fonctionne que pour les tableaux appropriés, et non pour les objets hash map. 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) par spécifiquement manipulant le premier et le dernier élément et passant en boucle sur les éléments du milieu.

La fonction array_keys peut être utilisée pour que la réponse efficace fonctionne de la manière suivante: 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 d’étalonnage sur ce point, mais aucune logique n’a été ajoutée à la boucle, c’est-à-dire si la performance est la plus touchée, alors je suppose que les repères fournis avec la réponse efficace sont assez proches.

Si vous vouliez fonctionnaliser ce genre de chose, j’ai pris un élan pour une telle fonction iterateList ici }. Bien que, vous souhaitiez peut-être comparer le code Gist si vous êtes extrêmement préoccupé par l'efficacité. Je ne sais pas combien de temps tout l'invocation de fonction introduit.

6
TheMadDeveloper

Pour les scripts de génération de requêtes SQL, ou tout ce qui effectue 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 acceptée actuelle utilise une boucle et un contrôle dans la boucle qui seront effectués chaque_single_iteration, la manière correcte (rapide) 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 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 évident que le coût du contrôle est élevé, et bien sûr, plus vous ajoutez de contrôles variables;)

5
Morg.

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

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

L'utilisation d'une variable booléenne reste la plus fiable, même si vous souhaitez vérifier la première apparition d'un $value (je l'ai trouvée 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 de !next( $array ) pour trouver le dernier $value qui retournera true s'il n'y a pas de valeur next() à itérer.

Et je préfère utiliser une boucle for à la place de foreach si je devais 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++;
}
4
5ervant

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.

3
paul

Pas sûr que ce soit 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;
        }
    }
}
1
vbarbarosh

Vous pouvez utiliser le compteur et la longueur du tableau.

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

 $ i = 0; 
 $ len = count ($ array); 
 foreach ($ array as $ item) {
 si ($ i === 0) {
 // premier
 } sinon si ($ i === $ len - 1) {
 // dernier
 } 
 //… 
 $ i ++; 
 } 
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 devraient être mentionnées:

  • Si votre tableau n’est pas indexé strictement (numériquement), vous devez d’abord le diriger vers array_values.
  • Si vous devez modifier le $element, vous devez le transmettre par référence (&$element).
  • Toutes les variables extérieures à la fonction anonyme dont vous avez besoin doivent être listées à côté de $indexOfLastElement dans la construction use, de nouveau par référence si nécessaire.
0
undko