wake-up-neo.com

Identifizieren der letzten Schleife bei Verwendung für jeden

Ich möchte mit der letzten Schleifeniteration etwas anderes machen, wenn Sie 'foreach' für ein Objekt ausführen. Ich verwende Ruby, aber das gleiche gilt für C #, Java usw.

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

Die gewünschte Ausgabe ist äquivalent zu:

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

Die offensichtliche Problemumgehung besteht darin, den Code mithilfe von 'for i in 1..list.length' in eine for-Schleife zu migrieren. Die Lösung für jede Lösung wirkt jedoch eleganter. Was ist die eleganteste Art, einen Sonderfall während einer Schleife zu codieren? Kann es mit foreach gemacht werden?

48
Matt

Wie wäre es, zunächst einen reference auf den last item zu bekommen und ihn dann zum Vergleich innerhalb der foreach-Schleife zu verwenden? Ich sage nicht, dass Sie dies tun sollten, da ich selbst die indexbasierte Schleife verwenden würde, wie von KlauseMeier erwähnt. Tut mir leid, dass ich Ruby nicht kenne. Das folgende Beispiel ist in C #! Ich hoffe es stört dich nicht :-)

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

Ich habe den folgenden Code überarbeitet, um den Vergleich anhand des Referenzwerts zu vergleichen (nur Referenztypen, nicht Werttypen). Der folgende Code sollte mehrere Objekte unterstützen, die dieselbe Zeichenfolge enthalten (jedoch nicht dasselbe Zeichenfolgenobjekt), da das Beispiel von MattChurcy nicht angegeben hat, dass die Zeichenfolgen eindeutig sein müssen. Statt den Index zu berechnen, verwende ich die LINQ-Methode.

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

Einschränkungen des obigen Codes. (1) Es kann nur für Zeichenfolgen oder Referenztypen und nicht für Werttypen verwendet werden. (2) Ein Objekt kann nur einmal in der Liste angezeigt werden. Sie können verschiedene Objekte mit demselben Inhalt haben. Literalzeichenfolgen können nicht wiederholt verwendet werden, da C # kein eindeutiges Objekt für Zeichenfolgen mit demselben Inhalt erstellt.

Und ich bin nicht dumm. Ich weiß, dass eine indexbasierte Schleife verwendet werden muss. Das habe ich bereits gesagt, als ich die erste Antwort zum ersten Mal veröffentlicht habe. Ich habe im context der Frage die bestmögliche Antwort geliefert. Ich bin zu müde, um das weiter zu erklären, also können Sie alle nur stimmen, um meine Antwort zu löschen. Ich bin so glücklich, wenn dieser weggeht. Vielen Dank 

23
anonymous

Das foreach-Konstrukt (definitiv in Java, wahrscheinlich auch in anderen Sprachen) soll die allgemeinste Art der Iteration darstellen, die die Iteration über Sammlungen ohne sinnvolle Iterationsreihenfolge einschließt. Zum Beispiel hat ein Hash-basiertes Set keine Reihenfolge und daher ist kein letztes Element ". Die letzte Iteration kann bei jeder Iteration ein anderes Element ergeben.

Grundsätzlich gilt: Nein, das Foreach-Konstrukt soll nicht so verwendet werden.

37

Ich sehe hier viel komplexen, schwer lesbaren Code ... Warum sollte man es nicht einfach halten?

var count = list.Length;

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

Es ist nur eine zusätzliche Aussage!

Eine andere häufige Situation ist, dass Sie mit dem letzten Element etwas mehr oder weniger tun möchten, z. B. ein Trennzeichen zwischen den Elementen:

var count = list.Length;

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

Ist das elegant genug? Es wird eine nicht leere Liste angenommen.

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

In Ruby würde ich in dieser Situation each_with_index verwenden

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

Sie können eine eachwithlast-Methode in Ihrer Klasse definieren, um dasselbe wie each für alle Elemente außer den letzten zu tun, aber für das letzte Element etwas anderes:

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

Dann könnten Sie es so nennen (foo ist eine Instanz von MyColl oder eine Unterklasse davon):

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

Editieren: Folgendem Vorschlag von 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 oder neuer

Zuerst würde ich eine Erweiterungsmethode schreiben:

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

Dann ist die Verwendung der neuen foreach sehr einfach:

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

Sie sollten foreach nur verwenden, wenn Sie each gleich behandeln. Verwenden Sie stattdessen eine indexbasierte Interation. Ansonsten müssen Sie eine andere Struktur um die Elemente hinzufügen, die Sie verwenden können, um das normale vom letzten Aufruf des foreach-Aufrufs zu unterscheiden (siehe gute Papiere über die Karte, die im Vergleich zu google für den Hintergrund reduziert wurde: http://labs.google .com/papers/mapreduce.html , map == foreach, reduziert == (zB Summe oder Filter).

Map kennt kein Wissen über die Struktur (insbesondere, an welcher Position sich ein Element befindet), es wandelt nur ein Element nach Element um (kein Wissen von einem Element kann zum Transformieren eines anderen verwendet werden!), Aber die Verwendung von redu kann einen Speicher verwenden, um beispielsweise zu zählen die Position und das letzte Element behandeln.

Ein üblicher Trick besteht darin, die Liste umzukehren und die erste zu behandeln (die jetzt einen bekannten Index = 0 hat) und später erneut umzukehren. (Was elegant aber nicht schnell ist;))

4
H2000

Foreach ist insofern elegant, als er sich nicht um die Anzahl der Elemente in einer Liste kümmert und jedes Element gleich behandelt. Ich denke, Ihre einzige Lösung wird eine for-Schleife sein, die entweder bei itemcount-1 stoppt und Sie dann Ihr letztes Element außerhalb von präsentieren die Schleife oder eine Bedingung innerhalb der Schleife, die diese bestimmte Bedingung behandelt, dh wenn (i == itemcount) {...} else {...}

3
Lazarus

Sie könnten so etwas tun (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 verfügt auch über 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]
}

BEARBEITEN:

Oder mit jeder (korrigierte TomatoGG- und Kirschstein-Lösung):

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

Oder

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

Wenn Sie eine Auflistung verwenden, die eine Count-Eigenschaft verfügbar macht - eine Annahme, die von vielen der anderen Antworten gemacht wurde, also mache ich es auch -, dann können Sie so etwas mit C # und LINQ tun:

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

Wenn wir außerdem davon ausgehen, dass die Elemente in der Sammlung direkt über den Index - der aktuell akzeptierten Antwort auf diese - zugegriffen werden können, ist eine einfache for-Schleife eleganter/lesbarer als eine foreach:

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

Wenn für die Auflistung keine Count -Eigenschaft verfügbar ist und nicht über den Index zugegriffen werden kann, gibt es keine elegante Möglichkeit, dies zu tun, zumindest nicht in C #. Eine fehlerbereinigte Variante von Thomas Levesques Antwort ist wahrscheinlich so nah wie Sie kommen.


Hier ist die fehlerbereinigte Version von Thomas Antwort :

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

Und so würde ich es in C # tun, wenn die Sammlung keine Count-Eigenschaft verfügbar macht und die Elemente nicht direkt über den Index zugänglich sind. (Beachten Sie, dass es keine foreach gibt und der Code nicht besonders knapp ist, aber er wird eine annehmbare Leistung über so ziemlich jede auflistende Sammlung liefern.)

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

Was Sie zu tun versuchen, scheint für die foreach-Schleife etwas zu weit fortgeschritten zu sein. Sie können jedoch Iterators explizit verwenden. Zum Beispiel würde ich in Java Folgendes schreiben:

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

Bei einigen Vorschlägen wird davon ausgegangen, dass Sie das letzte Element in der Liste vor Beginn der Schleife finden und dann jedes Element mit diesem Element vergleichen können. Wenn Sie dies effizient tun können, ist die zugrunde liegende Datenstruktur wahrscheinlich ein einfaches Array. Wenn dies der Fall ist, warum sollten Sie sich überhaupt mit dem Foreach beschäftigen? Einfach schreiben:

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

Wenn Sie ein Element nicht effizient von einer beliebigen Position abrufen können - als wäre die zugrunde liegende Struktur eine verknüpfte Liste -, dann war das Abrufen des letzten Elements wahrscheinlich eine sequentielle Suche der gesamten Liste. Abhängig von der Größe der Liste kann dies ein Leistungsproblem sein. Wenn dies eine häufig ausgeführte Funktion ist, möchten Sie möglicherweise ein Array oder eine ArrayList oder eine vergleichbare Struktur verwenden, um dies auf diese Weise zu tun.

Klingt für mich so, als ob Sie fragen: "Was ist der beste Weg, um eine Schraube mit einem Hammer einzusetzen?"

1
Jay

Wäre es eine praktikable Lösung für Ihren Fall, einfach die ersten/letzten Elemente aus Ihrem Array herauszunehmen, bevor Sie den "allgemeinen" Lauf jedes Laufs durchführen?

So was:

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

Ich weiß nicht, wie For-Each-Schleifen in anderen Sprachen funktionieren, außer in Java . In Java verwendet For-Each die Iterable-Schnittstelle, die von For-Each verwendet wird, um einen Iterator und eine Schleife damit zu erhalten. Iterator hat eine Methode hasNext, die Sie verwenden könnten, wenn Sie den Iterator in der Schleife sehen könnten .. Sie können den Trick tatsächlich ausführen, indem Sie einen bereits erhaltenen Iterator in ein Iterable-Objekt einschließen, sodass die for-Schleife das bekommt, was sie benötigt und was Sie bekommen können eine hasNext-Methode innerhalb der Schleife.

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

Sie können eine Klasse erstellen, die all diese iterierbaren und iteratorischen Elemente einschließt, sodass der Code folgendermaßen aussieht:

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

Ich glaube, ich bevorzuge die Lösung von kgiannakakis, jedoch könnte man so etwas immer tun.

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

Zumindest in C # ist dies ohne reguläre for-Schleife nicht möglich.

Der Enumerator der Auflistung entscheidet, ob ein nächstes Element vorhanden ist (MoveNext-Methode), die Schleife weiß dies nicht.

1
DanielR

Dieses Problem kann auf elegante Weise gelöst werden, indem Pattern Matching in einer funktionalen Programmiersprache wie F # verwendet wird:

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

Ähnlich wie bei kgiannakakis Antwort:

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

Wie wäre es mit diesem? Ich habe gerade ein bisschen Ruby gelernt. hehehe

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

Entfernen Sie das letzte aus der Liste und behalten Sie seinen Wert.

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

}
0
Andy A.

Ein anderes Muster, das funktioniert, ohne die foreach-Schleife neu schreiben zu müssen:

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

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

Auf diese Weise hält der Compiler die Schleife gerade, Iteratoren (einschließlich derer ohne Zählungen) funktionieren wie erwartet, und der letzte ist von den anderen getrennt.

Ich benutze dieses Muster auch, wenn ich möchte, dass etwas zwischen den Iterationen geschieht, aber nicht nach der letzten. In diesem Fall wird X normalerweise verwendet, delayed bezieht sich auf etwas anderes und die Verwendung von delayed ist nur am Anfang der Schleife und es ist nichts zu tun, nachdem die Schleife beendet ist.

0
shipr

Verwenden Sie nach Möglichkeit join.

Meistens ist das Trennzeichen zwischen allen Elementen das gleiche. Fügen Sie sie einfach mit der entsprechenden Funktion in Ihrer Sprache zusammen.

Ruby-Beispiel

puts array.join(", ")

Dies sollte 99% aller Fälle abdecken und das Array in Kopf und Schwanz aufteilen.

Ruby-Beispiel

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