Possible d'itérer en arrière à travers un foreach?

129

Je sais que je pourrais utiliser une forinstruction et obtenir le même effet, mais puis-je faire une boucle en arrière dans une foreachboucle en C #?

JL.
la source
6
Vous pouvez inverser l'élément (list.Reverse ()) dans la liste avant de le faire pour chacun.
Akanthan
Vous devrez instancier tous les éléments de l'énumération pour pouvoir inverser leur ordre. Cela peut ne pas être possible. Considérez la séquence: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }comment inverseriez-vous cela?
Suncat2000 le

Réponses:

84

Lorsque vous travaillez avec une liste (indexation directe), vous ne pouvez pas le faire aussi efficacement qu'en utilisant une forboucle.

Edit: ce qui signifie généralement que lorsque vous êtes capable d'utiliser une forboucle, c'est probablement la bonne méthode pour cette tâche. De plus, pour autant qu'elle foreachsoit implémentée dans l'ordre, la construction elle-même est construite pour exprimer des boucles qui sont indépendantes des index des éléments et de l'ordre d'itération, ce qui est particulièrement important dans la programmation parallèle . À mon avis , l'itération reposant sur l'ordre ne devrait pas être utilisée foreachpour la boucle.

Sam Harwell
la source
3
Je trouve votre dernière déclaration un peu trop générale. Il y a sûrement des cas où, par exemple, un IEnumerable d'un certain type doit être itéré dans l'ordre? N'utiliseriez-vous pas foreach dans ce cas? Que utiliseriez-vous?
avl_sweden
1
MISE À JOUR: Voir [la réponse de Bryan à une question similaire [( stackoverflow.com/a/3320924/199364 ), pour une réponse plus moderne, en utilisant Linq, et en discutant des deux listes, et d'autres énumérables.
ToolmakerSteve
MISE À JOUR: Jon Skeet a une réponse encore plus élégante, qui est maintenant possible, en utilisant " yield return".
ToolmakerSteve
2
J'ai eu la même réaction initiale que @avl_sweden - "l'itération reposant sur l'ordre ne devrait pas utiliser foreach" semble trop large. Oui, foreach est idéal pour exprimer des tâches parallèles indépendantes de l'ordre. MAIS c'est aussi une partie essentielle de la programmation moderne basée sur des itérateurs . Peut-être que le fait est qu'il serait plus clair / plus sûr s'il y avait deux mots - clés distincts , pour clarifier si l'on affirme que les itérations sont indépendantes de l'ordre? [Étant donné un langage comme Eiffel qui peut propager des contrats, de telles affirmations pourraient être prouvées vraies ou fausses, pour un code donné.]
ToolmakerSteve
2
L'idée que foreach "est construite pour exprimer des boucles indépendantes des index des éléments et de l'ordre d'itération" est incorrecte. La spécification du langage c # exige que chaque élément de processus soit dans l'ordre. Soit en utilisant MoveNext sur les itérateurs, soit en traitant les index en commençant à zéro et en incrémentant de un à chaque itération de tableaux.
Donald Rich
145

Si vous utilisez .NET 3.5, vous pouvez le faire:

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

Ce n'est pas très efficace car il doit passer par l'énumérateur avant de tout mettre sur une pile, puis tout ressortir dans l'ordre inverse.

Si vous avez une collection directement indexable (par exemple IList), vous devriez certainement utiliser une forboucle à la place.

Si vous êtes sur .NET 2.0 et que vous ne pouvez pas utiliser une boucle for (c'est-à-dire que vous n'avez qu'un IEnumerable), vous n'aurez qu'à écrire votre propre fonction Reverse. Cela devrait fonctionner:

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

Cela repose sur un comportement qui n'est peut-être pas si évident. Lorsque vous passez un IEnumerable au constructeur de pile, il l'itérera et poussera les éléments sur la pile. Lorsque vous parcourez ensuite la pile, les choses ressortent dans l'ordre inverse.

Ceci et la Reverse()méthode d'extension .NET 3.5 vont évidemment exploser si vous lui donnez un IEnumerable qui n'arrête jamais de renvoyer des éléments.

Matt Howells
la source
Aussi j'ai oublié de mentionner .net v2 seulement s'il vous plaît
JL.
4
Solution intéressante .NET 2.0.
RichardOD
11
Est-ce que je manque quelque chose ou votre solution .Net 3.5 ne fonctionne pas réellement? Reverse () inverse la liste en place et ne la renvoie pas. Dommage, car j'espérais une solution comme celle-là.
user12861
2
Certains administrateurs marquent cette réponse comme FAUX s'il vous plaît. Reverse () est un void [1] et donc l'exemple ci-dessus conduit à une erreur de compilation. [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD
6
@ user12861, Micky Duncan: la réponse n'est pas fausse, il vous manque quelque chose. Il existe une méthode Reverse sur System.Collections.Generic.List <T> qui effectue une inversion sur place. Dans .Net 3.5, il existe une méthode d'extension sur IEnumerable <T> nommée Reverse. J'ai changé l'exemple de var à IEnumerable <int> pour rendre cela plus explicite.
Matt Howells
55

Comme le dit 280Z28, IList<T>vous pouvez simplement utiliser l'index. Vous pouvez masquer cela dans une méthode d'extension:

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

Ce sera plus rapide que celui Enumerable.Reverse()qui met en mémoire tampon toutes les données en premier. (Je ne crois pas Reversequ'aucune optimisation ne soit appliquée de cette manière Count().) Notez que cette mise en mémoire tampon signifie que les données sont lues complètement lorsque vous commencez à itérer, alors que vous FastReverse"verrez" toutes les modifications apportées à la liste pendant que vous itérez. (Il se cassera également si vous supprimez plusieurs éléments entre les itérations.)

Pour les séquences générales, il n'y a aucun moyen d'itérer en sens inverse - la séquence pourrait être infinie, par exemple:

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

À quoi vous attendriez-vous si vous essayiez de répéter cela en sens inverse?

Jon Skeet
la source
Juste curiosité, pourquoi utiliser "> = 0" au lieu de "> -1"?
Chris S
15
> pourquoi utiliser "> = 0" au lieu de "> -1"? Parce que> = 0 communique mieux l'intention aux humains lisant le code. Le compilateur devrait être capable d'optimiser cela à l'équivalent> -1 si cela améliorerait les performances.
Mark Maslar
1
FastReverse (ces éléments IList <T>) doivent être FastReverse <T> (ces éléments IList <T>. :)
Rob
Fendre les bâtons 0 <= i est encore mieux. Après avoir enseigné les mathématiques à un certain nombre d'enfants au fil des ans et aidé les gens à coder, j'ai trouvé que cela réduisait considérablement les erreurs que les gens faisaient s'ils échangeaient TOUJOURS a> b en b <a car les choses se présentent alors dans l'ordre naturel d'une ligne. de nombres (ou l'axe X d'un système de coordonnées) - le seul inconvénient est si vous devez coller une équation dans XML, disons un .config
Eske Rahn
13

Avant d'utiliser foreachpour l'itération, inversez la liste par la reverseméthode:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
Prem
la source
2
identique à la réponse de Matt Howells de '09
Martin Schneider
Un mot d'avertissement sur ce qui myListest sera utile. IEnumerable.Reverse ne fonctionnera pas ici.
nawfal le
2
@ MA-Maddin non les deux sont différents. Je suppose que le répondeur s'appuie sur List <T> .Reverse qui est en place.
nawfal
En fait, je ne sais pas pourquoi j'ai dit ça. J'aurais dû ajouter plus de détails ... Quoi qu'il en soit, c'est un code déroutant car il myListsemble être de type System.Collections.Generic.List<System.Windows.Documents.List>(ou de tout autre Listtype personnalisé ) sinon ce code ne fonctionne pas: P
Martin Schneider
5

Parfois, vous n'avez pas le luxe de l'indexation, ou peut-être voulez-vous inverser les résultats d'une requête Linq, ou peut-être que vous ne voulez pas modifier la collection source, si l'un de ces éléments est vrai, Linq peut vous aider.

Une méthode d'extension Linq utilisant des types anonymes avec Linq Select pour fournir une clé de tri pour 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);
    }

Usage:

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

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

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

Il est nommé "Invert" car il est synonyme de "Reverse" et permet de lever l'ambiguïté avec l'implémentation List Reverse.

Il est également possible d'inverser certaines plages d'une collection, puisque Int32.MinValue et Int32.MaxValue sont hors de la plage de tout type d'index de collection, nous pouvons les exploiter pour le processus de commande; si un index d'élément est inférieur à la plage donnée, il est affecté Int32.MaxValue afin que son ordre ne change pas lors de l'utilisation de OrderByDescending, de même, les éléments à un index supérieur à la plage donnée, seront affectés Int32.MinValue, de sorte qu'ils apparaissent à la fin du processus de commande. Tous les éléments de la plage donnée reçoivent leur index normal et sont inversés en conséquence.

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

Usage:

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

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

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

Je ne suis pas sûr des performances de ces implémentations Linq par rapport à l'utilisation d'une liste temporaire pour envelopper une collection pour l'inversion.


Au moment de la rédaction de cet article, je n'étais pas au courant de la propre implémentation inverse de Linq, mais c'était amusant de résoudre ce problème. https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

Vorspire
la source
4

Si vous utilisez un List <T>, vous pouvez également utiliser ce code:

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

C'est une méthode qui écrit la liste inversée en elle-même.

Maintenant le foreach:

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

La sortie est:

3
2
1
th3s0urc3
la source
3

C'est possible si vous pouvez changer le code de collection qui implémente IEnumerable ou IEnumerable (par exemple votre propre implémentation de IList).

Créez un Iterator effectuant ce travail pour vous, par exemple comme l'implémentation suivante via l' interface IEnumerable (en supposant que 'items' est un champ List dans cet exemple):

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

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

Pour cette raison, votre liste itérera dans l'ordre inverse dans votre liste.

Juste un indice: vous devez clairement indiquer ce comportement spécial de votre liste dans la documentation (encore mieux en choisissant un nom de classe explicite comme Stack ou Queue, aussi).

Beachwalker
la source
2

Non. ForEach parcourt simplement la collection pour chaque élément et l'ordre dépend s'il utilise IEnumerable ou GetEnumerator ().

Josip Medved
la source
1
Eh bien, il existe des garanties de commande, selon le type de collecte.
Jon Skeet
1

Élaborant légèrement sur la belle réponse de Jon Skeet , cela pourrait être polyvalent:

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

Et puis utilisez comme

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

J'ai utilisé ce code qui a fonctionné

                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
la source
2
C'est mauvais car .Reverse()modifie en fait la liste.
Colin Basnett
-8

Cela fonctionne plutôt 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
la source
9
Cela me semble incroyablement inefficace.
Jean Azzopardi