Como determinar a primeira e a última iteração em um loop foreach?

494

A questão é simples. Eu tenho um foreachloop no meu código:

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

Nesse loop, quero reagir de maneira diferente quando estivermos na primeira ou na última iteração.

Como fazer isso?

mehdi
fonte

Respostas:

426

Você pode usar um contador:

$i = 0;
$len = count($array);
foreach ($array as $item) {
    if ($i == 0) {
        // first
    } else if ($i == $len - 1) {
        // last
    }
    // …
    $i++;
}
quiabo
fonte
24
Eu não acho que a votação devida ocorra aqui, pois isso também está funcionando corretamente e ainda não é tão ruim quanto usar array_shifte array_pop. Embora essa seja a solução que eu surgisse se tivesse que implementar uma coisa dessas, continuaria com a resposta do Rok Kralj .
shadyyx
15
Se eu precisar de um contador, prefiro usar o loop FOR em vez de FOREACH.
Rkawano
10
Se você usa $i = 1, não precisa se preocupar $len - 1, basta usar $len.
aksu
2
@Twan Como está o ponto 3, certo? Ou relevante para esta questão, uma vez que envolve HTML? Essa é uma pergunta do PHP, claramente ... e quando se trata de semântica de marcação, cabe a fatos muito mais profundos do que "um blahblah adequado é sempre melhor que blahblah (essa nem é minha opinião, é fato puro)".
Deji
1
@rkawano mas você não pode obter a chave chamada se você usar o laço FOR
Fahmi
1015

Se você preferir uma solução que não exija a inicialização do contador fora do loop, proponho comparar a chave de iteração atual com a função que indica a última / primeira chave da matriz.

Isso se torna um pouco mais eficiente (e mais legível) com o próximo PHP 7.3.

Solução para PHP 7.3 e superior:

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

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

Solução para todas as versões do 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
fonte
44
Resposta fantástica! Muito mais limpo do que usar várias matrizes.
Paul J
14
Isso deve borbulhar todo o caminho até o topo, porque é a resposta certa. Outra vantagem dessas funções em relação ao uso de array_shift e array_pop é que as matrizes são deixadas intactas, caso sejam necessárias posteriormente. +1 por compartilhar conhecimento apenas por isso.
Awemo 30/04/12
13
essa é definitivamente a melhor maneira, se você deseja manter seu código limpo. Eu estava prestes a votar novamente, mas agora não estou convencido de que a sobrecarga funcional desses métodos de matriz valha a pena. Se estamos apenas falando sobre o último elemento, então isso é end()+ key()em todas as iterações do loop - se são os dois, são 4 métodos sendo chamados sempre. É verdade que essas operações seriam muito leves e provavelmente são apenas pesquisas de ponteiro, mas os documentos continuam especificando isso reset()e end() modificando o ponteiro interno da matriz - então é mais rápido que um contador? possivelmente não.
pospi
19
Eu não acho que você deve emitir reset ($ array) dentro de um foreach. A partir da documentação oficial (www.php.net/foreach): "Como o foreach depende do ponteiro interno da matriz, alterá-lo dentro do loop pode levar a um comportamento inesperado". E redefinir faz exatamente isso (www.php.net/reset): "Defina o ponteiro interno de uma matriz como seu primeiro elemento"
Gonçalo Queirós
11
@ GonçaloQueirós: Funciona. O Foreach trabalha em uma cópia da matriz. No entanto, se você ainda estiver preocupado, sinta-se à vontade para mover a reset()chamada antes do foreach e armazenar em cache o resultado $first.
Rok Kralj
121

Para encontrar o último item, acho que esse código funciona sempre:

foreach( $items as $item ) {
    if( !next( $items ) ) {
        echo 'Last Item';
    }
}
Yojance
fonte
2
Isso tem muito poucos votos positivos, existe alguma desvantagem em usar isso?
precisa saber é o seguinte
2
@ Kevin Kuyl - Conforme mencionado por Pang acima, se a matriz contiver um item que o PHP avalie como falso (ou seja, 0, "", nulo), esse método terá resultados inesperados.
Alterei
4
isso é muito impressionante, mas para esclarecer o problema que outras pessoas apontam, ele sempre falha com uma matriz como essa [true,true,false,true]. Mas, pessoalmente, usarei isso sempre que estiver lidando com uma matriz que não contém booleano false.
precisa saber é
4
next()NUNCA deve ser usado dentro de um loop foreach. Ele atrapalha o ponteiro interno da matriz. Confira a documentação para mais informações.
Drazzah
89

Uma versão mais simplificada das opções acima e presumindo que você não esteja usando índices personalizados ...

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

Versão 2 - Porque vim detestar o uso do else a menos que seja necessário.

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

    if ($index == $len - 1) {
        // last
        // do something
        continue;
    }
}
Hayden
fonte
8
Isso funciona para objetos também. As outras soluções funcionam apenas para matrizes.
Lamy
1
Esta é a melhor resposta para mim, mas deve ser condensada, não adianta declarar o comprimento fora do loop foreach: if ($ index == count ($ array) -1) {...} #
181 Andrew #
2
@ Andrew dessa maneira, você continua contando os elementos da matriz, para cada iteração.
Pcarvalho
1
@ Peteroak Sim, na verdade, isso prejudicaria o desempenho tecnicamente, e dependendo da contagem ou do número de loops que poderiam ser significativos. Portanto, desconsidere meu comentário: D
Andrew
4
@peteroak @Andrew O número total de elementos em uma matriz é armazenado como uma propriedade internamente, para que não ocorram impactos no desempenho if ($index == count($array) - 1). Veja aqui .
GreeKatrina
36

Você pode remover o primeiro e o último elementos da matriz e processá-los separadamente.

Como isso:

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

A remoção de toda a formatação para CSS em vez de tags embutidas melhoraria seu código e aceleraria o tempo de carregamento.

Você também pode evitar misturar HTML com lógica php sempre que possível.

Sua página pode ficar muito mais legível e mantida separando coisas 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
fonte
20

Uma tentativa de encontrar o primeiro seria:

$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
fonte
3
Edite sua resposta para adicionar uma explicação de como seu código funciona e como ele resolve o problema do OP. Muitos pôsteres do SO são iniciantes e não entenderão o código que você postou.
alarmei o alien
4
Esta resposta não diz como determinar se você está na última iteração do loop. É, no entanto, uma tentativa válida de resposta e não deve ser sinalizada como não uma resposta. Se você não gostar, você deve votar e não sinalizá-lo.
ArtOfWarfare 27/10
É claro que, na primeira iteração, ele inserirá a primeira condição e, em seguida, alterará o valor para false, e dessa forma ele entrará na primeira iteração apenas uma vez.
Mohamed23gharbi
20

Simplesmente isso 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];
}

Obrigado @billynoah por resolver a questão final .

Sydwell
fonte
3
Melhor! Eu apenas esclareceria if ($key === $lastkey).
Krzysztof Przygoda
2
não deveria ser assim if ($lastkey === $key)?
Kinetic
1
Eu recebo:PHP Warning: key() expects parameter 1 to be array, integer given in php shell code on line 1
billynoah
1
@ Sydwell - leia o erro. key()está recebendo um número inteiro, não end(). end()" retorna o valor do último elemento " e key()espera uma matriz como entrada.
Billynoah
11

1: Por que não usar uma fordeclaração simples ? Supondo que você esteja usando uma matriz real e não uma, Iteratorvocê pode facilmente verificar se a variável do contador é 0 ou menos que o número inteiro de elementos. Na minha opinião, esta é a solução mais limpa e compreensível ...

$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: você deve considerar o uso de conjuntos aninhados para armazenar sua estrutura em árvore. Além disso, você pode melhorar tudo usando funções recursivas.

okoman
fonte
Se você estiver indo para usar um forque você pode fazer um loop a partir 1de n-1e tomar o ifé fora do corpo. Não faz sentido checá-los repetidamente.
MPEN
9

Melhor resposta:

$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
fonte
2
Isso falha quando você possui apenas um elemento na matriz.
Memochipan
4
É rápido e fácil, desde que você tenha certeza de que sempre há mais de um elemento na matriz (como disse Memochipan). Portanto, não é uma solução à prova de falhas para mim - nenhuma 'melhor resposta'.
Seika85
5
each () será DEPRECADO a partir do PHP 7.2.0 . Consulte também php.net/manual/en/function.each.php
Flow
8

A resposta mais eficiente do @morg, ao contrário foreach, funciona apenas para matrizes apropriadas, não para objetos de mapa de hash. Essa resposta evita a sobrecarga de uma declaração condicional para cada iteração do loop, como na maioria dessas respostas (incluindo a resposta aceita), manipulando especificamente o primeiro e o último elemento e repetindo os elementos do meio.

A array_keysfunção pode ser usada para fazer a resposta eficiente funcionar 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++;

Eu não fiz benchmarking sobre isso, mas nenhuma lógica foi adicionada ao loop, que foi o maior impacto no desempenho, então eu suspeitaria que os benchmarks fornecidos com a resposta eficiente estão bem próximos.

Se você quisesse funcionalizar esse tipo de coisa, aceitei uma função iterateList aqui . No entanto, convém comparar o código de essência se você estiver super preocupado com a eficiência. Não tenho certeza de quanto sobrecarga toda a chamada de função introduz.

TheMadDeveloper
fonte
6

Para scripts geradores de consultas SQL, ou qualquer coisa que execute uma ação diferente para o primeiro ou o último elemento, é muito mais rápido (quase o dobro da velocidade) evitar o uso de verificações desnecessárias de variáveis.

A solução atual aceita usa um loop e uma verificação dentro do loop que será feita a cada_a única_iteração, a maneira correta (rápida) de fazer isso é a seguinte:

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

Um pequeno benchmark caseiro mostrou o seguinte:

test1: 100000 execuções do modelo morg

tempo: 1869.3430423737 milissegundos

test2: 100000 execuções do modelo, se houver

tempo: 3235.6359958649 milissegundos

E, portanto, fica bem claro que a verificação custa muito e, é claro, fica ainda pior quanto mais verificações variáveis ​​você adicionar;)

Morg.
fonte
Seu código só funciona se você tiver certeza de que precisa ter chaves inteiras incrementais. $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++;irá produzir:0: , 1: One, 2: ,
Seika85
converter um mapa de hash para uma matriz é um comportamento indefinível, o "Objeto" array()criado é {'one':"1 1 1",0:"",1:"One",2:"",3:"",4:"Four"}mas os elementos vazios são ignorados com count, você está contando o número de "coisas" definidas !! SACRIFÍCIO RECONHECIDO! Esta resposta merece recompensa, mas se @Morg. se foi, é inútil. Eu daria recompensa a uma pessoa que provavelmente não usaria o SO novamente! Se ele voltar e melhorar sua resposta, ele merece a recompensa!
Mjz19910
Como observa o @ mjz19910, mapas e matrizes de hash não são intercambiáveis. No entanto, você pode obter as propriedades do hash com a array_keysfunção, que pode ser tratada como uma matriz. Veja minha resposta "aprimorada" .
TheMadDeveloper
Isso é o que eu uso para 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

Com Chaves e Valores, isso também funciona:

foreach ($array as $key => $value) {
    if ($value === end($array)) {
        echo "LAST ELEMENT!";
    }
}
Benibr
fonte
2
Dessa forma, você está comparando valores e não funciona se uma matriz contiver dois mesmos elementos.
Krzysztof Przygoda
5

O uso de uma variável booleana ainda é a mais confiável, mesmo se você quiser verificar a primeira aparência de a $value (achei mais útil na minha situação e em muitas situações) , como esta:

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

Então, que tal !next( $array )encontrar o último $valueque retornará truese não houver next()valor para iterar.

E eu prefiro usar um forloop em vez de foreachusar um 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++;
}
5ervant
fonte
4

Me deparei com esta discussão quando tenho o mesmo problema. Eu só preciso obter o primeiro elemento e depois re-analisar meu código até que isso me ocorreu.

$firstElement = true;

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

Os códigos acima são ótimos e completos, mas se você precisar apenas do primeiro elemento, poderá tentar esse código.

Paulo
fonte
2

Não tenho certeza se ainda é necessário. Mas a solução a seguir deve funcionar com iteradores e não requer 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
fonte
0

Você também pode usar uma função 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
    }
});

Mais três coisas devem ser mencionadas:

  • Se sua matriz não estiver indexada estritamente (numericamente), você deve canalizar sua matriz array_valuesprimeiro.
  • Se você precisar modificar, $elementprecisará passar por referência ( &$element).
  • Quaisquer variáveis ​​externas à função anônima de que você precisa dentro, será necessário listá-las ao lado de $indexOfLastElementdentro da useconstrução, novamente por referência, se necessário.
undko
fonte
0

Você pode usar o contador e o comprimento da matriz.

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

    $ i = 0;
    $ len = count (matriz $);
    foreach ($ array como $ item) {
        if ($ i === 0) {
            // primeiro
        } mais se ($ i === $ len - 1) {
            // último
        }
        //…
        $ i ++;
    }
Jesus Erwin Suarez
fonte
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();
   }
}
João Paulo Pinheiro
fonte
0

Usando 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

antelove
fonte
-2

Tente o seguinte:

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
fonte