¿Posible iterar hacia atrás a través de un foreach?

129

Sé que podría usar una fordeclaración y lograr el mismo efecto, pero ¿puedo recorrer hacia atrás a través de un foreachciclo en C #?

JL
fuente
66
Puede invertir el elemento (list.Reverse ()) en la lista antes de hacerlo para cada uno.
Akanthan
Tendría que crear instancias de todos los elementos en la enumeración para poder revertir su orden. Puede que no sea posible. Considere la secuencia: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }¿Cómo revertiría eso?
Suncat2000

Respuestas:

84

Al trabajar con una lista (indexación directa), no puede hacerlo de manera tan eficiente como usar un forbucle.

Editar: lo que generalmente significa que, cuando puede usar un forbucle, es probable que sea el método correcto para esta tarea. Además, en la medida en que foreachse implementa en orden, la construcción en sí misma se construye para expresar bucles que son independientes de los índices de elementos y el orden de iteración, lo cual es particularmente importante en la programación paralela . En mi opinión, la iteración que se basa en el orden no debería usarse foreachpara el bucle.

Sam Harwell
fuente
3
Su última declaración me parece demasiado general. Seguramente hay casos en los que, por ejemplo, un IEnumerable de algún tipo debe iterarse en orden. ¿No usarías foreach en ese caso? ¿Qué usarías?
avl_sweden
1
ACTUALIZACIÓN: Consulte [la respuesta de Bryan a una pregunta similar [( stackoverflow.com/a/3320924/199364 ), para obtener una respuesta más moderna, utilizando Linq y discutiendo ambas listas y otros enumerables.
ToolmakerSteve
ACTUALIZACIÓN: Jon Skeet tiene una respuesta aún más elegante, que ahora es posible, usando " yield return".
ToolmakerSteve
2
Tuve la misma reacción inicial que @avl_sweden: "la iteración basada en el orden no debe usar foreach" suena demasiado general. Sí, foreach es bueno para expresar tareas paralelas independientes del orden. PERO también es una parte esencial de la programación moderna basada en iteradores . Quizás el punto es que sería más claro / seguro si hubiera dos palabras clave distintas , para aclarar si uno está afirmando que las iteraciones son independientes del orden. [Dado un lenguaje como Eiffel que puede propagar contratos, tales afirmaciones pueden ser demostrablemente verdaderas o falsas, para un código dado.]
ToolmakerSteve
2
La idea de que foreach "se construye para expresar bucles que son independientes de los índices de elementos y el orden de iteración" es incorrecta. La especificación del lenguaje c # requiere que cada elemento del proceso esté en orden. Ya sea utilizando MoveNext en iteradores o procesando índices que comienzan en cero y se incrementan en uno en cada iteración de matrices.
Donald Rich
145

Si está en .NET 3.5 puede hacer esto:

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

No es muy eficiente, ya que básicamente tiene que pasar por el enumerador hacia adelante colocando todo en una pila y luego vuelve a mostrar todo en orden inverso.

Si tiene una colección indexable directamente (por ejemplo, IList), definitivamente debería usar un forbucle.

Si está en .NET 2.0 y no puede usar un bucle for (es decir, solo tiene un IEnumerable), entonces solo tendrá que escribir su propia función inversa. Esto debería funcionar:

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

Esto se basa en un comportamiento que quizás no sea tan obvio. Cuando pasa un IEnumerable al constructor de la pila, iterará a través de él y empujará los elementos a la pila. Cuando iteras a través de la pila, las cosas vuelven a aparecer en orden inverso.

Reverse()Obviamente, este y el método de extensión .NET 3.5 explotarán si lo alimenta con un IEnumerable que nunca deja de devolver elementos.

Matt Howells
fuente
También olvidé mencionar .net v2 solo por favor
JL.
44
Interesante solución .NET 2.0.
RichardOD
11
¿Me estoy perdiendo algo o su solución .Net 3.5 no funciona realmente? Reverse () invierte la lista en su lugar y no la devuelve. Lástima, ya que esperaba una solución como esa.
usuario12861
2
Algunos administradores marcan esta respuesta como INCORRECTA por favor. Reverse () es un vacío [1] y, por lo tanto, el ejemplo anterior conduce a un error de compilación. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD
66
@ user12861, Micky Duncan: la respuesta no es incorrecta, te falta algo. Hay un método inverso en System.Collections.Generic.List <T> que hace un reverso in situ. En .Net 3.5 hay un método de extensión en IEnumerable <T> llamado Reverse. He cambiado el ejemplo de var a IEnumerable <int> para hacerlo más explícito.
Matt Howells
55

Como dice 280Z28, para un IList<T>solo puede usar el índice. Puede ocultar esto en un método de extensión:

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

Esto será más rápido que lo Enumerable.Reverse()que almacena primero todos los datos. (No creo que Reversehaya ninguna optimización aplicada de la manera que lo Count()hace). Tenga en cuenta que este almacenamiento en búfer significa que los datos se leen completamente cuando comienza a iterar por primera vez, mientras FastReverseque "verá" cualquier cambio realizado en la lista mientras itera. (También se romperá si elimina varios elementos entre iteraciones).

Para secuencias generales, no hay forma de iterar en reversa: la secuencia podría ser infinita, por ejemplo:

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

¿Qué esperarías que pasara si intentaras iterar sobre eso a la inversa?

Jon Skeet
fuente
Solo curiosidad, ¿por qué usar "> = 0" en lugar de "> -1"?
Chris S
15
> ¿por qué usar "> = 0" en lugar de "> -1"? Porque> = 0 comunica mejor la intención a los humanos que leen el código. El compilador debería poder optimizar eso al equivalente> -1 si hacerlo mejoraría el rendimiento.
Mark Maslar
1
FastReverse (estos elementos IList <T>) deben ser FastReverse <T> (estos elementos IList <T>. :)
Rob
Dividir los palos 0 <= i es aún mejor. Después de enseñar un buen número de matemáticas a los niños a lo largo de los años y ayudar a las personas con la codificación, descubrí que reduce mucho los errores que cometen las personas si SIEMPRE intercambian a> b a b <a como las cosas van en el orden natural de una línea de números (o el eje X de un sistema de coordenadas): el único inconveniente es si necesita pegar una ecuación en XML, digamos un .config
Eske Rahn
13

Antes de usar foreachpara la iteración, invierta la lista por el reversemétodo:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
Prem
fuente
2
igual que la respuesta de Matt Howells del '09
Martin Schneider el
Una palabra de precaución sobre lo myListque será será útil. IEnumerable. Inversa no funcionará aquí.
nawfal
2
@ MA-Maddin no, los dos son diferentes. Supongo que el respondedor confía en la Lista <T>. Inversa que está en su lugar.
nawfal
En realidad, no sé por qué dije eso. Debería haber agregado más detalles ... De todos modos, este es un código confuso ya que myListparece ser de tipo System.Collections.Generic.List<System.Windows.Documents.List>(o cualquier otro Listtipo personalizado ) de lo contrario, este código no funciona: P
Martin Schneider
5

A veces no tiene el lujo de indexar, o tal vez desee revertir los resultados de una consulta de Linq, o tal vez no desee modificar la colección de origen, si alguno de estos es cierto, Linq puede ayudarlo.

Un método de extensión de Linq que utiliza tipos anónimos con Linq Select para proporcionar una clave de clasificación para 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"

Se llama "Invertir" porque es sinónimo de "Reversa" y permite la desambiguación con la implementación Lista inversa.

También es posible revertir ciertos rangos de una colección, ya que Int32.MinValue e Int32.MaxValue están fuera del rango de cualquier tipo de índice de colección, podemos aprovecharlos para el proceso de pedido; si un índice de elemento está por debajo del rango dado, se le asigna Int32.MaxValue para que su orden no cambie cuando se usa OrderByDescending, de manera similar, los elementos en un índice mayor que el rango dado, se asignarán Int32.MinValue, de modo que aparecer al final del proceso de pedido. A todos los elementos dentro del rango dado se les asigna su índice normal y se invierten en consecuencia.

    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"

No estoy seguro de los éxitos de rendimiento de estas implementaciones de Linq en comparación con el uso de una lista temporal para envolver una colección para revertirla.


Al momento de escribir, no estaba al tanto de la implementación inversa de Linq, aún así, fue divertido resolver esto. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

Vorspire
fuente
4

Si usa una Lista <T>, también puede usar este código:

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

Este es un método que escribe la lista inversa en sí misma.

Ahora el foreach:

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

El resultado es:

3
2
1
th3s0urc3
fuente
3

Es posible si puede cambiar el código de recopilación que implementa IEnumerable o IEnumerable (por ejemplo, su propia implementación de IList).

Cree un iterador que haga este trabajo por usted, por ejemplo, como la siguiente implementación a través de la interfaz IEnumerable (suponiendo que 'elementos' es un campo de lista en este ejemplo):

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

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

Debido a esto, su Lista iterará en orden inverso a través de su lista.

Solo una pista: debe indicar claramente este comportamiento especial de su lista dentro de la documentación (incluso mejor eligiendo un nombre de clase autoexplicativo como Stack o Queue, también).

Beachwalker
fuente
2

No. ForEach simplemente itera a través de la colección para cada artículo y el orden depende de si usa IEnumerable o GetEnumerator ().

Josip Medved
fuente
1
El pozo allí son garantías de orden, dependiendo del tipo de colección.
Jon Skeet el
1

Explicando ligeramente la buena respuesta de Jon Skeet , esto podría ser versátil:

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

Y luego usar como

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}
Eske Rahn
fuente
-1

He usado este código que funcionó

                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) {
Sanjoy Nath
fuente
2
Esto es malo porque en .Reverse()realidad está modificando la lista.
Colin Basnett
-8

Esto funciona bastante bien

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]);
}
Mc_Topaz
fuente
9
Esto me suena increíblemente ineficiente.
Jean Azzopardi