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?
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
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.
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(",");
}
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]
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
}
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 } )
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);
});
}
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;))
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 {...}
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);
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}"
}
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);
}
Ce que vous essayez de faire semble un peu trop avancé pour la boucle foreach. Cependant, vous pouvez utiliser Iterator
s 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);
}
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?"
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}"
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);
}
}
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
}
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.
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)
| [] -> ""
Similaire à la réponse de kgiannakakis:
list.first(list.size - 1).each { |i| puts "Looping: " + i }
puts "Last one: " + list.last
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}
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)
{
}
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.
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