¿Cómo determinar la primera y la última iteración en un bucle foreach?

494

La pregunta es simple. Tengo un foreachbucle en mi código:

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

En este ciclo, quiero reaccionar de manera diferente cuando estamos en la primera o la última iteración.

¿Como hacer esto?

mehdi
fuente

Respuestas:

426

Podrías usar un contador:

$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}
Gumbo
fuente
24
No creo que deba llevarse a cabo el downvoting ya que esto también funciona correctamente y todavía no es tan basura como usar array_shifty array_pop. Aunque esta es la solución que se me ocurrió si tuviera que implementar tal cosa, seguiría con la respuesta de Rok Kralj ahora.
shadyyx
15
Si necesito un contador, prefiero usar el bucle FOR en lugar de FOREACH.
rkawano
10
Si usa $i = 1, no tiene que preocuparse $len - 1, solo use $len.
aksu
2
@Twan ¿Cómo es el punto # 3 correcto? ¿O relevante en absoluto a esta pregunta ya que involucra HTML? Esta es una pregunta de PHP, claramente ... y cuando se trata de la semántica de marcado, se trata de hechos mucho más profundos que "un blahblah adecuado siempre es mejor que blahblah (esta ni siquiera es mi opinión, es un hecho puro)"
Deji
1
@rkawano pero no puedes obtener la clave con nombre si usas el bucle FOR
Fahmi
1016

Si prefiere una solución que no requiera la inicialización del contador fuera del ciclo, propongo comparar la clave de iteración actual con la función que le indica la última / primera clave de la matriz.

Esto se vuelve algo más eficiente (y más legible) con el próximo PHP 7.3.

Solución para PHP 7.3 y superior:

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

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

Solución para todas las versiones de 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
fuente
44
Fantástica respuesta! Mucho más limpio que usar un montón de matrices.
Paul J
14
Esto debería burbujear hasta la cima porque es la respuesta correcta. Otra ventaja de estas funciones sobre el uso de array_shift y array_pop es que las matrices se dejan intactas, en caso de que se necesiten en un momento posterior. +1 por compartir conocimiento solo por el bien de él.
Awemo
13
Esta es definitivamente la mejor manera si desea mantener limpio su código. Estaba a punto de votarlo, pero ahora no estoy convencido de que la sobrecarga funcional de esos métodos de matriz valga la pena. Si solo estamos hablando del último elemento, entonces eso es end()+ key()en cada iteración del bucle; si son ambos, se llaman 4 métodos cada vez. De acuerdo, esas serían operaciones muy livianas y probablemente sean solo búsquedas de puntero, pero luego los documentos continúan especificando eso reset()y end() modificando el puntero interno de la matriz, entonces, ¿es más rápido que un contador? posiblemente no.
pospi
19
No creo que deba emitir reset ($ array) dentro de un foreach. De la documentación oficial (www.php.net/foreach): "Como foreach se basa en el puntero interno de la matriz, cambiarlo dentro del ciclo puede conducir a un comportamiento inesperado". Y reset hace precisamente eso (www.php.net/reset): "Establecer el puntero interno de una matriz a su primer elemento"
Gonçalo Queirós
11
@ GonçaloQueirós: Funciona. Foreach funciona en una copia de la matriz. Sin embargo, si todavía está preocupado, siéntase libre de mover la reset()llamada antes del foreach y guardar el resultado en caché $first.
Rok Kralj
121

Para encontrar el último elemento, encuentro que este código funciona cada vez:

foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}
Yojance
fuente
2
Esto tiene muy pocos votos a favor, ¿hay algún inconveniente en usar esto?
Kevin Kuyl
2
@Kevin Kuyl: como mencionó Pang anteriormente, si la matriz contiene un elemento que PHP evalúa como falso (es decir, 0, "", nulo), este método tendrá resultados inesperados.
Modifiqué
44
Esto es en su mayoría muy impresionante, pero para aclarar el problema que otros señalan, invariablemente fallará con una matriz como [true,true,false,true]. Pero personalmente usaré esto cada vez que esté tratando con una matriz que no contiene boolean false.
billynoah
44
next()debe NUNCA ser utilizado dentro de un bucle foreach. Desordena el puntero interno de la matriz. Consulte la documentación para más información.
Drazzah
89

Una versión más simplificada de lo anterior y suponiendo que no esté utilizando índices personalizados ...

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

Versión 2 - Porque he detestado usar el else a menos que sea necesario.

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

    if ($index == $len - 1) {
        // last
        // do something
        continue;
    }
}
Hayden
fuente
8
Esto también funciona para los objetos. Las otras soluciones funcionan solo para matrices.
Lamy
1
Esta es la mejor respuesta para mí, pero debe condensarse, no hay punto que declare la longitud fuera del ciclo foreach: if ($ index == count ($ array) -1) {...}
Andrew
2
@Andrew de esa manera sigues contando los elementos de la matriz, para cada iteración.
pcarvalho
1
@peteroak Sí, en realidad perjudicaría técnicamente el rendimiento y, según lo que cuente o cuántos bucles puedan ser significativos. Así que ignore mi comentario: D
Andrew
44
@peteroak @Andrew El número total de elementos en una matriz se almacena internamente como una propiedad, por lo que no habría ningún impacto en el rendimiento al hacerlo if ($index == count($array) - 1). Ver aquí .
GreeKatrina
36

Puede eliminar el primer y el último elemento de la matriz y procesarlos por separado.

Me gusta esto:

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

Eliminar todo el formato a CSS en lugar de etiquetas en línea mejoraría su código y aceleraría el tiempo de carga.

También puede evitar mezclar HTML con lógica php siempre que sea posible.

Su página podría hacerse mucho más legible y mantenible separando cosas como esta:

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

Un intento de encontrar el primero sería:

$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
fuente
3
Edite su respuesta para agregar una explicación de cómo funciona su código y cómo resuelve el problema del OP. Muchos carteles SO son novatos y no entenderán el código que ha publicado.
Alarmé al extranjero el
44
Esta respuesta no dice cómo determinar si estás en la última iteración del ciclo. Sin embargo, es un intento válido de respuesta y no debe marcarse como no una respuesta. Si no te gusta, deberías votarlo en lugar de marcarlo.
ArtOfWarfare
Está claro, en la primera iteración ingresará la primera condición y luego cambiará su valor a falso, y de esta manera solo entrará en la primera iteración una vez.
Mohamed23gharbi
20

Simplemente esto funciona!

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

Gracias @billynoah por resolver el problema final .

Sydwell
fuente
3
¡Mejor! Solo aclararía if ($key === $lastkey).
Krzysztof Przygoda
2
no debería ser esto if ($lastkey === $key)?
Cinética
1
Obtengo:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
billynoah
1
@Sydwell: lee el error. key()está obteniendo un número entero, no end(). end()" devuelve el valor del último elemento " y key()espera una matriz como entrada.
billynoah
11

1: ¿Por qué no usar una fordeclaración simple ? Suponiendo que está utilizando una matriz real y no una Iterator, puede verificar fácilmente si la variable del contador es 0 o uno menos que la cantidad total de elementos. En mi opinión, esta es la solución más limpia y comprensible ...

$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: debe considerar el uso de conjuntos anidados para almacenar su estructura de árbol. Además, puede mejorar todo mediante el uso de funciones recursivas.

okoman
fuente
Si vas a usar un for, puedes desplazarte de un lado 1a otro n-1y sacar el ifs del cuerpo. No tiene sentido revisarlos repetidamente.
mpen
9

La mejor respuesta:

$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
fuente
2
Esto falla cuando solo tiene un elemento en la matriz.
Memochipan
44
Es rápido y fácil siempre que pueda estar seguro de que siempre hay más de un elemento en la matriz (como dijo Memochipan). Por lo tanto, no es una solución a prueba de fallas para mí, no hay 'mejor respuesta'.
Seika85
55
cada uno () será DEPRECADO a partir de PHP 7.2.0 . Consulte también php.net/manual/en/function.each.php
Flujo el
8

La respuesta más eficiente de @morg, a diferencia foreach, solo funciona para matrices adecuadas, no para objetos de mapa hash. Esta respuesta evita la sobrecarga de una declaración condicional para cada iteración del bucle, como en la mayoría de estas respuestas (incluida la respuesta aceptada) al manejar específicamente el primer y último elemento, y recorrer los elementos intermedios.

La array_keysfunción se puede utilizar para hacer que la respuesta eficiente funcione como 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++;

No he hecho una evaluación comparativa de esto, pero no se ha agregado ninguna lógica al ciclo, que es donde se produce el mayor éxito en el rendimiento, por lo que sospecho que los puntos de referencia proporcionados con la respuesta eficiente están bastante cerca.

Si quisieras funcionalizar este tipo de cosas, he dado un giro en esa función iterateList aquí . Sin embargo, es posible que desee comparar el código esencial si está muy preocupado por la eficiencia. No estoy seguro de cuánto sobrecarga introduce toda la invocación de función.

TheMadDeveloper
fuente
6

Para los scripts de generación de consultas SQL, o cualquier cosa que realice una acción diferente para el primer o el último elemento, es mucho más rápido (casi el doble de rápido) evitar el uso de comprobaciones de variables innecesarias.

La solución actual aceptada utiliza un bucle y una verificación dentro del bucle que se realizará en cada_site_iteración, la forma correcta (rápida) de hacer esto es la siguiente:

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

Un pequeño punto de referencia casero mostró lo siguiente:

test1: 100000 ejecuciones del modelo morg

tiempo: 1869.3430423737 milisegundos

test2: 100000 ejecuciones de modelo si duran

tiempo: 3235.6359958649 milisegundos

Y, por lo tanto, está bastante claro que el cheque cuesta mucho y, por supuesto, empeora aún más a medida que agrega más cheques;)

Morg
fuente
Su código solo funciona si puede estar seguro de tener que tener claves enteras incrementales. $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++;saldrá:0: , 1: One, 2: ,
Seika85
lanzar un mapa hash a una matriz es un comportamiento indefinible, el "Objeto" array()hecho es {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}pero los elementos vacíos se ignoran con el recuento, ¡estás contando el número de "cosas" definidas! ¡SACRIFICIO CON REGALO ENTRANTE! Esta respuesta merece recompensa, pero si @Morg. desaparecido, no tiene sentido. ¡Daría recompensas a una persona que probablemente no usará SO nuevamente! Si regresa y mejora su respuesta, ¡merece la recompensa!
mjz19910
Como señala @ mjz19910, los mapas hash y las matrices no son intercambiables. Sin embargo, puede obtener las propiedades del hash con la array_keysfunción, que puede tratar como una matriz. Vea mi respuesta "mejorada" .
TheMadDeveloper
Eso es lo que uso para la consulta:$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 Keys and Values ​​esto también funciona:

foreach ($array as $key => $value) {
    if ($value === end($array)) {
        echo "LAST ELEMENT!";
    }
}
Benibr
fuente
2
De esta manera, está comparando valores y no funciona si una matriz contiene dos mismos elementos.
Krzysztof Przygoda
5

El uso de una variable booleana sigue siendo el más confiable, incluso si desea verificar la primera aparición de un $value (lo encontré más útil en mi situación y en muchas situaciones) , como este:

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

Entonces, ¿qué tal !next( $array )encontrar el último $valueque devolverá truesi no hay ningún next()valor para iterar?

Y prefiero usar un forbucle en lugar de foreachusar un contador, como este:

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

Encontré este hilo cuando tengo el mismo problema. Solo necesito obtener el primer elemento, luego vuelvo a analizar mi código hasta que se me ocurre.

$firstElement = true;

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

Los códigos anteriores son geniales y completos, pero si solo necesita el primer elemento, puede probar este código.

Pablo
fuente
2

No estoy seguro si aún es necesario. Pero la siguiente solución debería funcionar con iteradores y no requiere 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
fuente
0

También puede usar una función anónima:

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

Se deben mencionar tres cosas más:

  • Si su matriz no está indexada estrictamente (numéricamente) array_valuesprimero debe canalizarla .
  • Si necesita modificar el $element, debe pasarlo por referencia ( &$element).
  • Cualquier variable de fuera de la función anónima que necesita dentro, tendrá que enumerarla junto al $indexOfLastElementinterior de la useconstrucción, nuevamente por referencia si es necesario.
undko
fuente
0

Puede usar el contador y la longitud de la matriz.

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

    $ i = 0;
    $ len = cuenta ($ array);
    foreach ($ array como $ item) {
        if ($ i === 0) {
            // primero
        } else if ($ i === $ len - 1) {
            // último
        }
        // ...
        $ i ++;
    }
Jesus Erwin Suarez
fuente
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
fuente
0

Utilizando reset ($ array) y 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

antílope
fuente
-2

Prueba esto:

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
fuente