Czy można iterować wstecz przez foreach?


129

Wiem, że mogę użyć forinstrukcji i osiągnąć ten sam efekt, ale czy mogę wykonać pętlę wstecz przez foreachpętlę w języku C #?


6
Możesz odwrócić element (list.Reverse ()) na liście, zanim zrobisz to dla każdego.
Akanthan


Musiałbyś utworzyć wystąpienie wszystkich elementów w wyliczeniu, abyś mógł odwrócić ich kolejność. To może nie być możliwe. Rozważ sekwencję: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Jak byś to odwrócił?
Suncat2000

Odpowiedzi:


84

Podczas pracy z listą (indeksowanie bezpośrednie) nie można tego robić tak wydajnie, jak używanie forpętli.

Edit: co generalnie oznacza, gdy jesteś w stanie użyć forpętli, prawdopodobnie poprawna metoda dla tego zadania. Dodatkowo, o ile foreachjest implementowana w kolejności, sama konstrukcja jest zbudowana do wyrażania pętli niezależnych od indeksów elementów i kolejności iteracji, co jest szczególnie ważne w programowaniu równoległym . To jest moja opinia , że iteracja polegając na celu nie należy używać foreachdo pętli.


3
Twoje ostatnie stwierdzenie wydaje mi się zbyt ogólne. Z pewnością są przypadki, w których na przykład jakiś rodzaj IEnumerable musi być iterowany po kolei? Czy w takim przypadku nie użyłbyś foreach? Czego byś użył?
avl_sweden

1
AKTUALIZACJA: Zobacz odpowiedź [Bryana na podobne pytanie [( stackoverflow.com/a/3320924/199364 ), aby uzyskać bardziej nowoczesną odpowiedź, używając Linq i omawiając obie listy i inne wyliczalne.
ToolmakerSteve

AKTUALIZACJA: Jon Skeet ma jeszcze bardziej elegancką odpowiedź, która jest teraz możliwa przy użyciu „ yield return”.
ToolmakerSteve

2
Miałem taką samą początkową reakcję jak @avl_sweden - „iteracja oparta na kolejności nie powinna używać foreach” brzmi zbyt szeroko. Tak, foreach jest dobre do wyrażania niezależnych od kolejności zadań równoległych. ALE jest to również istotna część nowoczesnego programowania opartego na iteratorach . Być może chodzi o to, że byłoby jaśniej / bezpieczniej, gdyby istniały dwa odrębne słowa kluczowe , aby wyjaśnić, czy jedno z nich twierdzi, że iteracje są niezależne od kolejności? [Biorąc pod uwagę język taki jak Eiffel, który może propagować kontrakty, takie twierdzenia mogą być prawdziwe lub fałszywe dla danego kodu.]
ToolmakerSteve

2
Pomysł, że foreach „jest zbudowany do wyrażania pętli niezależnych od indeksów elementów i kolejności iteracji” jest niepoprawny. Specyfikacja języka C # wymaga uporządkowania wszystkich elementów procesu. Albo za pomocą MoveNext na iteratorach, albo przez przetwarzanie indeksów zaczynających się od zera i zwiększanych o jeden przy każdej iteracji tablic.
Donald Rich,

145

Jeśli korzystasz z .NET 3.5, możesz to zrobić:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

Nie jest to zbyt wydajne, ponieważ w zasadzie musi przejść przez moduł wyliczający do przodu, umieszczając wszystko na stosie, a następnie wyrzucając wszystko z powrotem w odwrotnej kolejności.

Jeśli masz kolekcję, którą można bezpośrednio indeksować (np. IList), zdecydowanie powinieneś forzamiast tego użyć pętli.

Jeśli korzystasz z .NET 2.0 i nie możesz użyć pętli for (np. Masz tylko IEnumerable), będziesz musiał po prostu napisać własną funkcję Reverse. To powinno działać:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

Zależy to od pewnego zachowania, które być może nie jest takie oczywiste. Po przekazaniu elementu IEnumerable do konstruktora stosu, będzie on iterował przez niego i wypycha elementy na stos. Kiedy następnie przechodzisz przez stos, wyrzuca elementy z powrotem w odwrotnej kolejności.

Ta i Reverse()metoda rozszerzenia .NET 3.5 oczywiście wybuchną, jeśli podasz mu IEnumerable, który nigdy nie przestanie zwracać elementów.


Zapomniałem też wspomnieć tylko o .net v2
JL.

4
Ciekawe rozwiązanie .NET 2.0.
RichardOD

11
Czy coś mi brakuje lub czy Twoje rozwiązanie .Net 3.5 w rzeczywistości nie działa? Reverse () odwraca listę w miejscu i nie zwraca jej. Szkoda, bo liczyłem na takie rozwiązanie.
user12861

2
Niektórzy administratorzy oznaczyli tę odpowiedź jako NIEPRAWIDŁOWĄ. Reverse () jest void [1], więc powyższy przykład prowadzi do błędu kompilacji. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD,

6
@ user12861, Micky Duncan: odpowiedź nie jest zła, czegoś brakuje. W System.Collections.Generic.List <T> istnieje metoda Reverse, która wykonuje odwrócenie w miejscu. W .Net 3.5 istnieje metoda rozszerzająca IEnumerable <T> o nazwie Reverse. Zmieniłem przykład z var na IEnumerable <int>, aby było to bardziej wyraźne.
Matt Howells

55

Jak mówi 280Z28, IList<T>możesz po prostu użyć indeksu. Możesz to ukryć w metodzie rozszerzenia:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

Będzie to szybsze niż pierwsze Enumerable.Reverse()buforowanie wszystkich danych. (Nie sądzę Reverse, aby zastosowano jakieś optymalizacje w taki sposób Count()). Zauważ, że to buforowanie oznacza, że ​​dane są odczytywane w całości przy pierwszym uruchomieniu iteracji, podczas FastReversegdy podczas iteracji "zobaczy" wszelkie zmiany dokonane na liście. (Zepsuje się również, jeśli usuniesz wiele elementów między iteracjami).

W przypadku sekwencji ogólnych nie ma możliwości wykonywania iteracji w odwrotnej kolejności - sekwencja może być nieskończona, na przykład:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

Czego byś się spodziewał, gdybyś próbował powtórzyć to w odwrotnej kolejności?


Tylko ciekawość, po co używać „> = 0” zamiast „> -1”?
Chris S,

15
> po co używać „> = 0” zamiast „> -1”? Ponieważ> = 0 lepiej przekazuje intencję ludziom czytającym kod. Kompilator powinien być w stanie zoptymalizować to do odpowiednika> -1, jeśli poprawiłoby to wydajność.
Mark Maslar

1
FastReverse (te elementy IList <T>) powinny być FastReverse <T> (te elementy IList <T>. :)
Rob

Rozłupywanie prętów 0 <= i jest jeszcze lepsze. Po nauczeniu przez lata wielu dzieci matematyki i pomocy ludziom w programowaniu, odkryłem, że znacznie zmniejsza to liczbę błędów popełnianych przez ludzi, jeśli ZAWSZE zamieniają a> b na b <a, ponieważ rzeczy przybierają wtedy naturalną kolejność linii liczb (Lub oś X układu współrzędnych) - jedyną wadą jest wklejenie równania do XML, powiedzmy a .config
Eske Rahn

13

Przed użyciem foreachdo iteracji odwróć listę za pomocą reversemetody:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }

2
tak samo jak odpowiedź Matta Howellsa z '09
Martin Schneider

Słowo ostrzeżenia na temat tego, co myListjest pomocne. IEnumerable.Reverse nie będzie tutaj działać.
nawfal

2
@ MA-Maddin nie, te dwa są różne. Domyślam się, że osoba odpowiadająca polega na liście <T> .Reverse, która jest na miejscu.
nawfal

Właściwie nie wiem, dlaczego to powiedziałem. Powinienem był dodać więcej szczegółów ... W każdym razie jest to mylący kod, ponieważ myListwydaje się być typu System.Collections.Generic.List<System.Windows.Documents.List>(lub innego Listtypu niestandardowego ), w przeciwnym razie ten kod nie działa: P
Martin Schneider

5

Czasami nie masz luksusu indeksowania, a może chcesz cofnąć wyniki zapytania Linq, a może nie chcesz modyfikować kolekcji źródłowej, jeśli którekolwiek z nich jest prawdą, Linq może ci pomóc.

Metoda rozszerzenia Linq używająca anonimowych typów z Linq Select w celu zapewnienia klucza sortowania dla Linq OrderByDescending;

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Stosowanie:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

Nazywa się „Invert”, ponieważ jest synonimem „Reverse” i umożliwia ujednoznacznienie z implementacją List Reverse.

Możliwe jest również odwrócenie pewnych zakresów kolekcji, ponieważ Int32.MinValue i Int32.MaxValue są poza zakresem jakiegokolwiek indeksu kolekcji, możemy je wykorzystać do procesu zamawiania; jeśli indeks elementu jest poniżej podanego zakresu, jest przypisywany Int32.MaxValue, aby jego kolejność nie zmieniała się podczas korzystania z OrderByDescending, podobnie elementy o indeksie większym niż podany zakres zostaną przypisane Int32.MinValue, więc pojawiają się na końcu procesu składania zamówienia. Wszystkim elementom w podanym zakresie przypisuje się normalny indeks i odpowiednio je odwraca.

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Stosowanie:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

Nie jestem pewien wydajności tych implementacji Linq w porównaniu z użyciem tymczasowej listy do pakowania kolekcji do cofania.


W momencie pisania nie byłem świadomy własnej implementacji Reverse firmy Linq, ale praca nad tym była zabawna. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

Jeśli używasz List <T>, możesz również użyć tego kodu:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

Jest to metoda, która sama w sobie zapisuje listę w odwrotnej kolejności.

Teraz foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

Wynik to:

3
2
1

3

Jest to możliwe, jeśli możesz zmienić kod kolekcji, który implementuje IEnumerable lub IEnumerable (np. Własną implementację IList).

Utwórz Iterator wykonujący to zadanie za Ciebie, na przykład jak następująca implementacja za pośrednictwem interfejsu IEnumerable (zakładając, że „items” to pole listy w tym przykładzie):

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Z tego powodu Twoja lista będzie iterować w odwrotnej kolejności na liście.

Wskazówka: powinieneś jasno określić to szczególne zachowanie swojej listy w dokumentacji (jeszcze lepiej, wybierając samoobjaśniającą się nazwę klasy, taką jak Stack lub Queue).


2

Nie. ForEach po prostu wykonuje iterację kolekcji dla każdego elementu, a zamówienie zależy od tego, czy używa IEnumerable, czy GetEnumerator ().


1
Cóż, gwarancje porządku w zależności od rodzaju kolekcji.
Jon Skeet

1

Opierając się słabo na ładnej odpowiedzi Jona Skeeta , może to być wszechstronne:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

A następnie użyj jako

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}

-1

Użyłem tego kodu, który zadziałał

                if (element.HasAttributes) {

                    foreach(var attr in element.Attributes().Reverse())
                    {

                        if (depth > 1)
                        {
                            elements_upper_hierarchy_text = "";
                            foreach (var ancest  in element.Ancestors().Reverse())
                            {
                                elements_upper_hierarchy_text += ancest.Name + "_";
                            }// foreach(var ancest  in element.Ancestors())

                        }//if (depth > 1)
                        xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                    }// foreach(var attr in element.Attributes().Reverse())

                }// if (element.HasAttributes) {

2
To jest złe, ponieważ .Reverse()faktycznie modyfikuje listę.
Colin Basnett,

-8

Działa to całkiem nieźle

List<string> list = new List<string>();

list.Add("Hello");
list.Add("Who");
list.Add("Are");
list.Add("You");

foreach (String s in list)
{
    Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
}

9
Brzmi to dla mnie niesamowicie nieefektywnie.
Jean Azzopardi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.