É possível iterar para trás através de um foreach?

129

Eu sei que poderia usar uma forinstrução e obter o mesmo efeito, mas posso fazer um loop para trás através de um foreachloop em c #?

JL.
fonte
6
Você pode reverter o elemento (list.Reverse ()) na lista antes de fazer cada um.
Akanthan
Você precisaria instanciar todos os elementos na enumeração para poder reverter a ordem deles. Pode não ser possível. Considere a sequência: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }Como você reverteria isso?
Suncat2000 30/01

Respostas:

84

Ao trabalhar com uma lista (indexação direta), você não pode fazê-lo tão eficientemente quanto usar um forloop.

Editar: o que geralmente significa que, quando você é capaz de usar um forloop, é provavelmente o método correto para esta tarefa. Além disso, na medida em que foreaché implementado em ordem, o próprio construto é construído para expressar loops independentes dos índices de elementos e da ordem de iteração, o que é particularmente importante na programação paralela . É minha opinião que a iteração que depende de ordem não deve ser usada foreachpara loop.

Sam Harwell
fonte
3
Acho sua última afirmação um pouco genérica demais. Certamente há casos em que, por exemplo, um IEnumerable de algum tipo precisa ser iterado em ordem? Você não usaria foreach nesse caso? O que você usaria?
precisa saber é
1
ATUALIZAÇÃO: Consulte [A resposta de Bryan para uma pergunta semelhante [( stackoverflow.com/a/3320924/199364 )), para obter uma resposta mais moderna, usando o Linq, e discutindo as duas listas e outros enumeráveis.
Home
ATUALIZAÇÃO: Jon Skeet tem uma resposta ainda mais elegante, que agora é possível, usando " yield return".
Home
2
Tive a mesma reação inicial que @avl_sweden - "a iteração que depende da ordem não deve usar foreach" soa muito ampla. Sim, o foreach é bom para expressar tarefas paralelas independentes de ordem. MAS também é uma parte essencial da programação moderna baseada em iterador . Talvez o ponto seja que seria mais claro / seguro se houvesse duas palavras-chave distintas , para esclarecer se alguém está afirmando que as iterações são independentes de ordem? [Dada uma linguagem como Eiffel que podem propagar contratos, tais afirmações poderiam ser comprovadamente verdadeiro ou falso, para determinado código.]
ToolmakerSteve
2
A ideia de que foreach "é criada para expressar loops independentes de índices de elementos e ordem de iteração" está incorreta. A especificação da linguagem c # requer que cada elemento do processo esteja em ordem. Usando MoveNext em iteradores ou processando índices começando em zero e incrementando em um em cada iteração de matrizes.
Donald Rich
145

Se você estiver no .NET 3.5, poderá fazer o seguinte:

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

Não é muito eficiente, pois precisa passar basicamente pelo enumerador, colocando tudo em uma pilha e exibindo tudo de volta na ordem inversa.

Se você tem uma coleção indexável diretamente (por exemplo, IList), definitivamente deve usar um forloop.

Se você está no .NET 2.0 e não pode usar um loop for (ou seja, você apenas possui um IEnumerable), basta escrever sua própria função Reverse. Isso deve funcionar:

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

Isso depende de algum comportamento que talvez não seja tão óbvio. Quando você passa um IEnumerable para o construtor de pilha, ele percorre e empurra os itens para a pilha. Quando você percorre a pilha, as coisas retornam na ordem inversa.

Esse Reverse()método de extensão .NET 3.5 obviamente explodirá se você alimentar um IEnumerable que nunca interrompa o retorno de itens.

Matt Howells
fonte
Também esqueci de mencionar .net v2 apenas por favor
JL.
4
Solução .NET 2.0 interessante.
31410 RichardOD
11
Estou faltando alguma coisa ou a sua solução .Net 3.5 não funciona realmente? Reverse () inverte a lista no local e não a retorna. Pena que eu esperava uma solução como essa.
user12861
2
Alguns administradores marcam esta resposta como ERRADA, por favor. Reverse () é um nulo [1] e, portanto, o exemplo acima leva a um erro de compilação. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD
6
@ user12861, Micky Duncan: a resposta não está errada, está faltando alguma coisa. Há um método Reverse em System.Collections.Generic.List <T> que faz um inverso no local. No .Net 3.5, existe um método de extensão no IEnumerable <T> chamado Reverse. Alterei o exemplo de var para IEnumerable <int> para tornar isso mais explícito.
precisa
55

Como 280Z28 diz, para um IList<T>você pode apenas usar o índice. Você pode ocultar isso em um método de extensão:

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

Isso será mais rápido do Enumerable.Reverse()que o buffer de todos os dados primeiro. (Não acredito que Reversetenham sido aplicadas otimizações dessa maneira Count().) Observe que esse buffer significa que os dados são lidos completamente quando você inicia a iteração, enquantoFastReverse "verá" as alterações feitas na lista durante a iteração. (Ele também será interrompido se você remover vários itens entre as iterações.)

Para seqüências gerais, não há como iterar ao contrário - a sequência pode ser infinita, por exemplo:

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

O que você esperaria que acontecesse se tentasse repetir isso inversamente?

Jon Skeet
fonte
Apenas curiosidade, por que usar "> = 0" em vez de "> -1"?
Chris S
15
> por que usar "> = 0" em vez de "> -1"? Porque> = 0 comunica melhor a intenção aos humanos que leem o código. O compilador deve poder otimizar isso para o equivalente a -1, se isso melhorar o desempenho.
27230 Mark Maslar
1
FastReverse (este IList <T> itens) deve ser FastReverse <T> (este IList <T> itens :).
Rob
Dividir varas 0 <= i é ainda melhor. Depois de ensinar um monte de matemática para crianças ao longo dos anos e ajudar as pessoas com a codificação, descobri que isso reduz muito os erros que as pessoas cometem se SEMPRE trocarem a> b para b <a, pois as coisas vêm na ordem natural de uma linha de números (ou o eixo X de um sistema de coordenadas) - o único inconveniente é se você precisa colar uma equação em XML, digamos, um .config
Eske Rahn
13

Antes de usar foreachpara iteração, inverta a lista pelo reversemétodo:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
Prem
fonte
2
mesmo que a resposta de Matt Howells de '09
Martin Schneider
Uma palavra de cautela sobre o que myListé será útil. IEnumerable.Reverse não funcionará aqui.
Nawfal # 26/18
2
@ MA-Maddin não, os dois são diferentes. Eu acho que o respondente está contando com a lista <T> .Reverse, que está no lugar.
Nawfal
Na verdade, não sei por que disse isso. Eu deveria ter adicionado mais detalhes ... De qualquer forma, esse código é confuso, pois myListparece ser do tipo System.Collections.Generic.List<System.Windows.Documents.List>(ou qualquer outro Listtipo personalizado ), caso contrário, esse código não funciona: P
Martin Schneider
5

Às vezes, você não tem o luxo de indexar, ou talvez queira reverter os resultados de uma consulta do Linq, ou talvez não queira modificar a coleção de fontes, se alguma for verdadeira, o Linq pode ajudá-lo.

Um método de extensão Linq usando tipos anônimos com Linq Select para fornecer uma chave de classificação 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"

É nomeado "Inverter" porque é sinônimo de "Reverso" e permite a desambiguação com a implementação de Reversão de Lista.

Também é possível reverter certos intervalos de uma coleção, já que Int32.MinValue e Int32.MaxValue estão fora do intervalo de qualquer tipo de índice de coleção, podemos aproveitá-los para o processo de pedido; se um índice de elemento estiver abaixo do intervalo especificado, ele será designado Int32.MaxValue para que sua ordem não seja alterada ao usar OrderByDescending; da mesma forma, os elementos em um índice maior que o intervalo especificado serão atribuídos Int32.MinValue, para que eles aparecem até o final do processo de pedido. Todos os elementos dentro do intervalo determinado recebem seu índice normal e são revertidos de acordo.

    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"

Não tenho certeza dos resultados de desempenho dessas implementações do Linq versus o uso de uma lista temporária para agrupar uma coleção para reversão.


No momento em que escrevi, eu não estava ciente da implementação reversa do Linq, ainda assim, foi divertido trabalhar com isso. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

Vorspire
fonte
4

Se você usa uma lista <T>, também pode usar este código:

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

Este é um método que grava a lista em si mesma.

Agora o foreach:

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

A saída é:

3
2
1
th3s0urc3
fonte
3

É possível se você pode alterar o código da coleção que implementa IEnumerable ou IEnumerable (por exemplo, sua própria implementação do IList).

Crie um iterador fazendo esse trabalho para você, por exemplo, como a seguinte implementação por meio da interface IEnumerable (supondo que 'itens' seja um campo Lista neste exemplo):

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

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

Por esse motivo, sua Lista irá repetir a ordem inversa na sua lista.

Apenas uma dica: você deve indicar claramente esse comportamento especial da sua lista na documentação (melhor ainda, escolhendo um nome de classe autoexplicativo, como Pilha ou Fila, também).

Beachwalker
fonte
2

Não. O ForEach apenas percorre a coleção para cada item e o pedido depende se ele usa IEnumerable ou GetEnumerator ().

Josip Medved
fonte
1
Bem lá são garantias de ordem, dependendo do tipo de coleção.
31909 Jon Skeet
1

Ao elaborar com leveza a bela resposta de Jon Skeet , isso pode 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];
}

E então use como

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

Eu usei esse código que funcionou

                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
fonte
2
Isso é ruim porque .Reverse()está realmente modificando a lista.
Colin Basnett
-8

Isso funciona muito bem

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
fonte
9
Isso me parece incrivelmente ineficiente.
21711 Jean Azzopardi