È possibile iterare all'indietro attraverso una foreach?


129

So che potrei usare forun'istruzione e ottenere lo stesso effetto, ma posso fare un ciclo indietro in un foreachciclo in C #?


6
Puoi invertire l'elemento (list.Reverse ()) nell'elenco prima di farlo per ciascuno.
Akanthan,


Dovresti istanziare tutti gli elementi dell'enumerazione in modo da poter invertire il loro ordine. Potrebbe non essere possibile. Considera la sequenza: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }come la invertiresti?
Suncat 2000,

Risposte:


84

Quando si lavora con un elenco (indicizzazione diretta), non è possibile farlo in modo efficiente come l'utilizzo di un forciclo.

Modifica: in genere significa che, quando si è in grado di utilizzare un forciclo, è probabilmente il metodo corretto per questa attività. Inoltre, per quanto foreachimplementato in ordine, il costrutto stesso è costruito per esprimere loop indipendenti dagli indici degli elementi e dall'ordine di iterazione, che è particolarmente importante nella programmazione parallela . E ' mia opinione che l'iterazione basandosi su ordine non dovrebbe usare foreachper loop.


3
Trovo la tua ultima affermazione un po 'troppo generale. Sicuramente ci sono casi in cui, per esempio, un IEnumerable di qualche tipo ha bisogno di essere ripetuto in ordine? Non useresti foreach in quel caso? Cosa useresti?
avl_sweden,

1
AGGIORNAMENTO: vedi [La risposta di Bryan a una domanda simile [( stackoverflow.com/a/3320924/199364 ), per una risposta più moderna, usando Linq e discutendo di entrambi gli elenchi e altri enumerabili.
ToolmakerSteve

AGGIORNAMENTO: Jon Skeet ha una risposta ancora più elegante, che ora è possibile, usando " yield return".
ToolmakerSteve

2
Ho avuto la stessa reazione iniziale di @avl_sweden - "iterazioni basate sull'ordine non dovrebbero usare foreach" suona troppo ampio. Sì, foreach è utile per esprimere attività parallele indipendenti dall'ordine. MA è anche una parte essenziale della moderna programmazione basata su iteratori . Forse il punto è che sarebbe più chiaro / più sicuro se esistessero due parole chiave distinte , per chiarire se si sta affermando che le iterazioni sono indipendenti dall'ordine? [Dato un linguaggio come Eiffel in grado di propagare i contratti, tali affermazioni potrebbero essere dimostrate vere o false, per un determinato codice.]
ToolmakerSteve

2
L'idea che foreach "sia stata creata per esprimere loop indipendenti dagli indici degli elementi e dall'ordine di iterazione" non è corretta. La specifica del linguaggio c # richiede che tutti gli elementi del processo siano in ordine. O usando MoveNext sugli iteratori o elaborando gli indici iniziando da zero e incrementando di uno su ogni iterazione di array.
Donald Rich,

145

Se sei su .NET 3.5 puoi farlo:

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

Non è molto efficiente in quanto deve sostanzialmente passare attraverso l'enumeratore in avanti mettendo tutto in pila, quindi rimuove tutto in ordine inverso.

Se disponi di una raccolta indicizzabile direttamente (ad esempio IList), devi forinvece utilizzare un ciclo.

Se sei su .NET 2.0 e non puoi usare un ciclo for (cioè hai solo un IEnumerable) allora dovrai solo scrivere la tua funzione Reverse. Questo dovrebbe funzionare:

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

Ciò si basa su un comportamento che forse non è così ovvio. Quando si passa un IEnumerable al costruttore dello stack, scorrerà attraverso di esso e spingerà gli elementi nello stack. Quando si scorre attraverso la pila, le cose vengono rimosse in ordine inverso.

Questo e il Reverse()metodo di estensione .NET 3.5 ovviamente esploderanno se lo alimenterai con un IEnumerable che non smette mai di restituire articoli.


Inoltre ho dimenticato di menzionare .net v2 solo per favore
JL.

4
Interessante soluzione .NET 2.0.
RichardOD,

11
Mi sto perdendo qualcosa o la tua soluzione .Net 3.5 non funziona davvero? Reverse () inverte l'elenco in posizione e non lo restituisce. Peccato, poiché speravo in una soluzione del genere.
user12861

2
Alcuni amministratori contrassegnano questa risposta come SBAGLIATA per favore. Reverse () è un vuoto [1] e quindi l'esempio sopra riportato porta a un errore di compilazione. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD

6
@ user12861, Micky Duncan: la risposta non è sbagliata, ti stai perdendo qualcosa. Esiste un metodo Reverse su System.Collections.Generic.List <T> che esegue un reverse sul posto. In .Net 3.5 esiste un metodo di estensione su IEnumerable <T> denominato Reverse. Ho cambiato l'esempio da var a IEnumerable <int> per renderlo più esplicito.
Matt Howells,

55

Come dice 280Z28, per un IList<T>puoi semplicemente usare l'indice. Puoi nasconderlo in un metodo di estensione:

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

Questo sarà più veloce di quello Enumerable.Reverse()che buffer prima tutti i dati. (Non credo che Reversesiano state applicate ottimizzazioni nel modo in cui lo Count()fa.) Si noti che questo buffering significa che i dati vengono letti completamente quando si inizia a iterare per la prima volta, mentre FastReverse"vedranno" eventuali modifiche apportate all'elenco mentre si esegue l'iterazione. (Si romperà anche se rimuovi più elementi tra le iterazioni.)

Per le sequenze generali, non c'è modo di iterare al contrario: la sequenza potrebbe essere infinita, ad esempio:

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

Cosa ti aspetteresti che accadesse se provassi a scorrere su quello al contrario?


Solo curiosità, perché usare "> = 0" invece di "> -1"?
Chris S,

15
> perché usare "> = 0" invece di "> -1"? Perché> = 0 comunica meglio l'intento agli umani che leggono il codice. Il compilatore dovrebbe essere in grado di ottimizzarlo con l'equivalente> -1 se ciò migliorerebbe le prestazioni.
Mark Maslar,

1
FastReverse (questa voce IList <T>) dovrebbe essere FastReverse <T> (questa voce IList <T>. :)
Rob

Dividere i bastoncini 0 <= i è ancora meglio. Dopo aver insegnato un bel numero di bambini nel corso degli anni e aiutato le persone con la programmazione, ho scoperto che riduce notevolmente gli errori che le persone commettono se scambiano SEMPRE da a> b a b <a come le cose poi arrivano nell'ordine naturale di una riga di numeri (o l'asse X di un sistema di coordinate) - l'unico inconveniente è se devi incollare un'equazione in XML, ad esempio un .config
Eske Rahn,

13

Prima di utilizzare foreachper l'iterazione, invertire l'elenco con il reversemetodo:

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


Una parola di cautela su ciò che myListè sarà utile. IEnumerable.Reverse non funzionerà qui.
nawfal,

2
@ MA-Maddin no i due sono diversi. Immagino che il risponditore faccia affidamento su Elenco <T>. Inverso che è in atto.
nawfal,

In realtà, non so perché l'ho detto. Avrei dovuto aggiungere ulteriori dettagli ... Comunque questo è un codice confuso poiché myListsembra essere di tipo System.Collections.Generic.List<System.Windows.Documents.List>(o qualsiasi altro Listtipo personalizzato ) altrimenti questo codice non funziona: P
Martin Schneider,

5

A volte non hai il lusso di indicizzare, o forse vuoi invertire i risultati di una query Linq, o forse non vuoi modificare la raccolta dei sorgenti, se qualcuno di questi è vero, Linq può aiutarti.

Un metodo di estensione Linq che utilizza tipi anonimi con Linq Select per fornire una chiave di ordinamento per 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);
    }

Uso:

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

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

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

Si chiama "Invert" perché è sinonimo di "Reverse" e consente di chiarire le ambizioni con l'implementazione di List Reverse.

È possibile invertire anche determinati intervalli di una raccolta, poiché Int32.MinValue e Int32.MaxValue sono al di fuori dell'intervallo di qualsiasi tipo di indice di raccolta, possiamo sfruttarli per il processo di ordinazione; se un indice di elemento è al di sotto dell'intervallo dato, viene assegnato Int32.MaxValue in modo che il suo ordine non cambi quando si utilizza OrderByDescending, allo stesso modo, agli elementi in un indice maggiore dell'intervallo dato, verrà assegnato Int32.MinValue, in modo che sembra alla fine del processo di ordinazione. A tutti gli elementi all'interno dell'intervallo specificato viene assegnato il loro indice normale e vengono invertiti di conseguenza.

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

Uso:

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

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

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

Non sono sicuro dei risultati delle prestazioni di queste implementazioni Linq rispetto all'uso di un elenco temporaneo per racchiudere una raccolta per l'inversione.


Al momento della stesura, non ero a conoscenza dell'implementazione inversa di Linq, tuttavia, è stato divertente farlo. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

Se usi un Elenco <T>, puoi anche usare questo codice:

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

Questo è un metodo che scrive la lista al contrario in sé.

Ora la foreach:

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

L'output è:

3
2
1

3

È possibile se è possibile modificare il codice di raccolta che implementa IEnumerable o IEnumerable (ad es. La propria implementazione di IList).

Crea un Iteratore che fa questo lavoro per te, ad esempio come la seguente implementazione attraverso l' interfaccia IEnumerable (supponendo che 'items' sia un campo Elenco in questo esempio):

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

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

Per questo motivo, l'elenco verrà ripetuto in ordine inverso nell'elenco.

Solo un suggerimento: dovresti dichiarare chiaramente questo comportamento speciale della tua lista all'interno della documentazione (ancora meglio scegliendo un nome di classe che si spiega da solo come Stack o Queue).


2

No. ForEach esegue l'iterazione della raccolta per ciascun elemento e l'ordine dipende dal fatto che utilizzi IEnumerable o GetEnumerator ().


1
Bene, ci sono garanzie di ordine, a seconda del tipo di raccolta.
Jon Skeet,

1

Elaborando leggermente la bella risposta di Jon Skeet , questo potrebbe essere versatile:

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

E poi usa come

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

-1

Ho usato questo codice che ha funzionato

                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
Questo è male perché .Reverse()sta effettivamente modificando l'elenco.
Colin Basnett,

-8

Funziona abbastanza bene

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
Questo mi sembra incredibilmente inefficiente.
Jean Azzopardi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.