web-dev-qa-db-fra.com

Identifier la dernière boucle en utilisant pour chaque

Je veux faire quelque chose de différent avec la dernière itération de la boucle lors de l'exécution "foreach" sur un objet. J'utilise Ruby, mais il en va de même pour C #, Java, etc.

  list = ['A','B','C']
  list.each{|i|
    puts "Looping: "+i # if not last loop iteration
    puts "Last one: "+i # if last loop iteration
  }

La sortie souhaitée est équivalente à:

  Looping: 'A'
  Looping: 'B'
  Last one: 'C'

La solution évidente consiste à migrer le code dans une boucle for à l'aide de 'for i in 1..list.length', mais la solution for each semble plus élégante. Quel est le moyen le plus gracieux de coder un cas spécial pendant une boucle? Peut-on le faire avec foreach?

48
Matt

Que diriez-vous d’obtenir un référence au dernier élément en premier et de l’utiliser ensuite pour la comparaison dans la boucle foreach? Je ne dis pas que vous devriez le faire car j'utiliserais moi-même la boucle basée sur un index comme mentionné par KlauseMeier. Et désolé, je ne connais pas Ruby, donc l'exemple suivant est en C #! J'espère que ça ne vous dérange pas :-)

string lastItem = list[list.Count - 1];
foreach (string item in list) {
    if (item != lastItem)
        Console.WriteLine("Looping: " + item);
    else    Console.Writeline("Lastone: " + item);
}

J'ai révisé le code suivant pour comparer par référence et non par valeur (ne peut utiliser que des types de référence et non des types de valeur). le code suivant devrait prendre en charge plusieurs objets contenant la même chaîne (mais pas le même objet chaîne), car l'exemple de MattChurcy n'indique pas que les chaînes doivent être distinctes et j'ai utilisé la méthode LINQ Last au lieu de calculer l'index.

string lastItem = list.Last();
foreach (string item in list) {
    if (!object.ReferenceEquals(item, lastItem))
        Console.WriteLine("Looping: " + item);
    else        Console.WriteLine("Lastone: " + item);
}

Limitations du code ci-dessus. (1) Cela ne fonctionne que pour les chaînes ou les types de référence, pas les types de valeur. (2) Le même objet ne peut apparaître qu'une fois dans la liste. Vous pouvez avoir différents objets contenant le même contenu. Les chaînes littérales ne peuvent pas être utilisées à plusieurs reprises car C # ne crée pas d'objet unique pour les chaînes ayant le même contenu.

Et je ne suis pas stupide. Je sais qu'une boucle basée sur un index est celle à utiliser. Je l'ai déjà dit dès que j'ai posté la réponse initiale. J'ai fourni la meilleure réponse possible dans le contexte de la question. Je suis trop fatigué pour continuer à expliquer cela, alors pouvez-vous voter pour supprimer ma réponse? Je serai si heureux si celui-ci s'en va. Merci 

23
anonymous

La construction foreach (en Java certainement, probablement aussi dans d’autres langages) est censée représenter le type le plus général si l’itération, qui inclut l’itération sur des collections qui n’ont pas d’ordre d’itération significatif. Par exemple, un ensemble basé sur un hachage n'a pas d'ordre, et donc il n'est pas "dernier élément". La dernière itération peut générer un élément différent à chaque itération.

Fondamentalement: non, la construction foreach n'est pas conçue pour être utilisée de cette façon.

37
Michael Borgwardt

Je vois beaucoup de code complexe, à peine lisible ici ... pourquoi ne pas le garder simple:

var count = list.Length;

foreach(var item in list)
    if (--count > 0)
        Console.WriteLine("Looping: " + item);
    else
        Console.Writeline("Lastone: " + item);

Ce n'est qu'une déclaration supplémentaire!

Une autre situation courante est que vous voulez faire quelque chose de plus ou de moins avec le dernier élément, comme mettre un séparateur entre les éléments:

var count = list.Length;

foreach(var item in list)
{
    Console.Write(item);
    if (--count > 0)
        Console.Write(",");
}
37
Bigjim

Est-ce assez élégant? Cela suppose une liste non vide.

  list[0,list.length-1].each{|i|
    puts "Looping:"+i # if not last loop iteration
  }
  puts "Last one:" + list[list.length-1]
22
kgiannakakis

En Ruby j'utiliserais each_with_index dans cette situation

list = ['A','B','C']
last = list.length-1
list.each_with_index{|i,index|
  if index == last 
    puts "Last one: "+i 
  else 
    puts "Looping: "+i # if not last loop iteration
  end
}
13
David Burrows

Vous pouvez définir une méthode eachwithlast dans votre classe pour qu'elle fasse la même chose que each sur tous les éléments sauf le dernier, mais avec un autre pour le dernier

class MyColl
  def eachwithlast
    for i in 0...(size-1)
      yield(self[i], false)
    end
    yield(self[size-1], true)
  end
end

Ensuite, vous pourriez l'appeler comme ceci (foo étant une instance de MyColl ou une de ses sous-classes):

foo.eachwithlast do |value, last|
  if last
    puts "Last one: "+value
  else
    puts "Looping: "+value
  end
end

Edit: Suivant la suggestion de molf:

class MyColl
  def eachwithlast (defaultAction, lastAction)
    for i in 0...(size-1)
      defaultAction.call(self[i])
    end
    lastAction.call(self[size-1])
  end
end

foo.eachwithlast(
    lambda { |x| puts "looping "+x },
    lambda { |x| puts "last "+x } )
9
Svante

C # 3.0 ou plus récent

Tout d'abord, j'écrirais une méthode d'extension:

public static void ForEachEx<T>(this IEnumerable<T> s, Action<T, bool> act)
{
    IEnumerator<T> curr = s.GetEnumerator();

    if (curr.MoveNext())
    {
        bool last;

        while (true)
        {
            T item = curr.Current;
            last = !curr.MoveNext();

            act(item, last);

            if (last)
                break;
        }
    }
}

Ensuite, utiliser la nouvelle foreach est très simple:

int[] lData = new int[] { 1, 2, 3, 5, -1};

void Run()
{
    lData.ForEachEx((el, last) =>
    {
        if (last)
            Console.Write("last one: ");
        Console.WriteLine(el);
    });
}
5
bohdan_trotsenko

Vous ne devez utiliser foreach que si vous manipulez chacun de la même manière. Utilisez plutôt une interaction basée sur un index. Sinon, vous devez ajouter une structure différente autour des éléments, que vous pouvez utiliser pour différencier la normale de la dernière dans l'appel foreach (regardez les bons articles sur la carte réduite de Google pour l'arrière-plan: http://labs.google. .com/papers/mapreduce.html , map == foreach, réduit == par exemple sum ou filter).

Map n'a aucune connaissance sur la structure (en particulier sur la position d'un élément), il ne transforme qu'un élément à la fois (aucune connaissance d'un élément ne peut être utilisée pour transformer un autre!), Mais peut utiliser une mémoire pour compter, par exemple la position et gérer le dernier élément.

Une astuce courante consiste à inverser la liste et à gérer le premier (qui a maintenant un index connu = 0), puis à appliquer de nouveau l'inverse. (Ce qui est élégant mais pas rapide;))

4
H2000

Foreach est élégant en ce sens qu'il ne se préoccupe pas du nombre d'éléments dans une liste et traite chaque élément de manière égale. Je pense que votre seule solution consistera à utiliser une boucle for qui s'arrête à itemcount-1, puis vous présentez votre dernier élément en dehors de la boucle ou une condition dans la boucle qui gère cette condition spécifique, c'est-à-dire if (i == itemcount) {...} else {...}

3
Lazarus

Vous pouvez faire quelque chose comme ça (C #):

string previous = null;
foreach(string item in list)
{
    if (previous != null)
        Console.WriteLine("Looping : {0}", previous);
    previous = item;
}
if (previous != null)
    Console.WriteLine("Last one : {0}", previous);
3
Thomas Levesque

Ruby a aussi la méthode each_index:

list = ['A','B','C']
list.each_index{|i|
  if i < list.size - 1
    puts "Looping:"+list[i]
  else
    puts "Last one:"+list[i]
}

MODIFIER:

Ou en utilisant chacune (solutions TomatoGG et Kirschstein corrigées):

list = ['A', 'B', 'C', 'A']
list.each { |i|
   if (i.object_id != list.last.object_id)
      puts "Looping:#{i}"
   else
      puts "Last one:#{i}"
   end
}

Looping:A
Looping:B
Looping:C
Last one:A

Ou

list = ['A', 'B', 'C', 'A']
list.each {|i| 
  i.object_id != list.last.object_id ? puts "Looping:#{i}" : puts "Last one:#{i}"       
}
2
klew

Si vous utilisez une collection qui expose une propriété Count - une hypothèse faite par beaucoup d'autres réponses, je vais donc le faire aussi - alors vous pouvez faire quelque chose comme ceci en utilisant C # et LINQ:

foreach (var item in list.Select((x, i) => new { Val = x, Pos = i }))
{
    Console.Write(item.Pos == (list.Count - 1) ? "Last one: " : "Looping: ");
    Console.WriteLine(item.Val);
}

Si nous supposons en outre que les éléments de la collection sont accessibles directement par index - la réponse actuellement acceptée suppose ceci -, une boucle simple for sera plus élégante/lisible qu'un foreach:

for (int i = 0; i < list.Count; i++)
{
    Console.Write(i == (list.Count - 1) ? "Last one: " : "Looping: ");
    Console.WriteLine(list[i]);
}

Si la collection n'expose pas de propriété Count et ne peut pas être consultée par index, il n'existe alors aucun moyen élégant de le faire, du moins pas en C #. Une variante corrigée par un bogue de la réponse de Thomas Levesque est probablement aussi proche que possible.


Voici la version corrigée par les bogues de Thomas répond :

string previous = null;
bool isFirst = true;
foreach (var item in list)
{
    if (!isFirst)
    {
        Console.WriteLine("Looping: " + previous);
    }
    previous = item;
    isFirst = false;
}
if (!isFirst)
{
    Console.WriteLine("Last one: " + previous);
}

Et voici comment je le ferais en C # si la collection n'expose pas une propriété Count et que les éléments ne sont pas directement accessibles par index. (Notez qu'il n'y a pas de foreach et que le code n'est pas particulièrement succinct, mais il donnera des performances décentes par rapport à la plupart des collections énumérables.)

// i'm assuming the non-generic IEnumerable in this code
// wrap the enumerator in a "using" block if dealing with IEnumerable<T>
var e = list.GetEnumerator();
if (e.MoveNext())
{
    var item = e.Current;

    while (e.MoveNext())
    {
        Console.WriteLine("Looping: " + item);
        item = e.Current;
    }
    Console.WriteLine("Last one: " + item);
}
2
LukeH

Ce que vous essayez de faire semble un peu trop avancé pour la boucle foreach. Cependant, vous pouvez utiliser Iterators explicitement. Par exemple, en Java, j'écrirais ceci:

Collection<String> ss = Arrays.asList("A","B","C");
Iterator<String> it = ss.iterator();
while (it.hasNext()) {
    String s = it.next();
    if(it.hasNext())
        System.out.println("Looping: " + s);
    else 
        System.out.println("Last one: " + s);
}
2
Bruno De Fraine

Je remarque un certain nombre de suggestions supposant que vous pouvez trouver le dernier élément de la liste avant de commencer la boucle, puis comparer chaque élément à cet élément. Si vous pouvez le faire efficacement, la structure de données sous-jacente est probablement un tableau simple. Si tel est le cas, pourquoi se soucier de la foreach? Ecrivez:

for (int x=0;x<list.size()-1;++x)
{
  System.out.println("Looping: "+list.get(x));
}
System.out.println("Last one: "+list.get(list.size()-1));

Si vous ne pouvez pas récupérer efficacement un élément à partir d'une position arbitraire - comme pour la structure sous-jacente est une liste chaînée -, l'obtention du dernier élément implique probablement une recherche séquentielle de la liste entière. Selon la taille de la liste, cela peut être un problème de performances. S'il s'agit d'une fonction fréquemment exécutée, vous pouvez envisager d'utiliser un tableau, ArrayList ou une structure comparable pour pouvoir le faire de cette manière.

Il me semble que vous demandez: "Quel est le meilleur moyen de visser une vis avec un marteau?", Alors que la meilleure question à poser est "Quel est le bon outil à utiliser pour mettre une vis?"

1
Jay

Serait-il une solution viable pour votre cas de ne prendre que les premiers/derniers éléments de votre tableau avant de faire le "général" de chaque exécution?

Comme ça:

list = ['A','B','C','D']
first = list.shift
last = list.pop

puts "First one: #{first}"
list.each{|i|
  puts "Looping: "+i
}
puts "Last one: #{last}"
1
Carlos Lima

Je ne sais pas comment les boucles for-each fonctionnent dans d'autres langages, mais Java . En Java, for for each utilise l'interface Iterable utilisée par for-each pour obtenir un itérateur et le boucler. Iterator a une méthode hasNext que vous pouvez utiliser si vous pouviez voir l'itérateur dans la boucle . Vous pouvez réellement faire l'affaire en entourant un Iterator déjà obtenu dans un objet Iterable afin que la boucle for ait ce dont elle a besoin une méthode hasNext à l'intérieur de la boucle.

List<X> list = ...

final Iterator<X> it = list.iterator();
Iterable<X> itw = new Iterable<X>(){
  public Iterator<X> iterator () {
    return it;
  }
}

for (X x: itw) {
  doSomething(x);
  if (!it.hasNext()) {
    doSomethingElse(x);
  }
}

Vous pouvez créer une classe qui englobe tous ces éléments itératifs et itérateurs de sorte que le code ressemble à ceci:

IterableIterator<X> itt = new IterableIterator<X>(list);
for (X x: itit) {
  doSomething(x);
  if (!itit.hasNext()) {
    doSomethingElse(x);
  }
}
1
aalku

Je pense que je préfère la solution de kgiannakakis , mais vous pouvez toujours faire quelque chose comme ça;

list = ['A','B','C']
list.each { |i|
   if (i != list.last)
      puts "Looping:#{i}"
   else
      puts "Last one:#{i}"
   end
}
1
Kirschstein

Au moins en C #, ce n’est pas possible sans une boucle for régulière.

L'énumérateur de la collection décide si un élément suivant existe (méthode MoveNext), la boucle n'est pas au courant.

1
DanielR

Ce problème peut être résolu de manière élégante en utilisant la correspondance de modèles dans un langage de programmation fonctionnel tel que F #:

let rec printList (ls:string list) = 
    match ls with
        | [last] -> "Last " + last
        | head::rest -> "Looping " + head + "\n" + printList (rest)
        | [] -> ""
1
eulerfx

Similaire à la réponse de kgiannakakis:

list.first(list.size - 1).each { |i| puts "Looping: " + i }
puts "Last one: " + list.last
0
Firas Assaad

Celui-ci, ça va? viens d'apprendre un peu de Ruby. hehehe

list.collect {|x|(x!=list.last ? "Looping:"+x:"Lastone:"+x) }.each{|i|puts i}      
0
anonymous

Supprimez le dernier de la liste et conservez sa valeur.

Spec spec = specs.Find(s=>s.Value == 'C');
  if (spec != null)
  {
    specs.Remove(spec);
  }
foreach(Spec spec in specs)
{

}
0
Andy A.

Un autre modèle qui fonctionne, sans avoir à réécrire la boucle foreach:

var delayed = null;
foreach (var X in collection)
{
    if (delayed != null)
    {
        puts("Looping");
        // Use delayed
    }
    delayed = X;
}

puts("Last one");
// Use delayed

De cette façon, le compilateur garde la boucle droite, les itérateurs (y compris ceux sans nombre) fonctionnent comme prévu et le dernier est séparé des autres.

J'utilise également ce modèle lorsque je souhaite que quelque chose se passe entre deux itérations, mais pas après la dernière. Dans ce cas, X est utilisé normalement, delayed fait référence à quelque chose d'autre, et l'utilisation de delay est seulement au début de la boucle et rien ne doit être fait après la fin de la boucle.

0
shipr

Utilisez join chaque fois que possible.

Le plus souvent, le délimiteur est le même entre tous les éléments, il suffit de les associer à la fonction correspondante dans votre langue.

Exemple Ruby,

puts array.join(", ")

Cela devrait couvrir 99% de tous les cas, et sinon diviser le tableau en tête et en queue.

Exemple Ruby,

*head, tail = array
head.each { |each| put "looping: " << each }
puts "last element: " << tail 
0
akuhn