Préserver l'ordre avec LINQ

361

J'utilise les instructions LINQ to Objects sur un tableau ordonné. Quelles opérations ne dois-je pas faire pour être sûr que l'ordre du tableau n'est pas modifié?

Matthieu Durut
la source

Réponses:

645

J'ai examiné les méthodes de System.Linq.Enumerable , en rejetant toutes celles qui renvoyaient des résultats non IEnumerable. J'ai vérifié les remarques de chacun pour déterminer en quoi l'ordre du résultat différerait de l'ordre de la source.

Préserve l'ordre absolument. Vous pouvez mapper un élément source par index à un élément de résultat

  • AsEnumerable
  • Jeter
  • Concat
  • Sélectionner
  • ToArray
  • Lister

Préserve l'ordre. Les éléments sont filtrés ou ajoutés, mais pas réorganisés.

  • Distinct
  • Sauf
  • Couper
  • OfType
  • Prepend (nouveau dans .net 4.7.1)
  • Sauter
  • SkipWhile
  • Prendre
  • TakeWhile
  • Zip (nouveau dans .net 4)

Détruit l'ordre - nous ne savons pas dans quel ordre s'attendre.

  • ToDictionary
  • Pour rechercher

Redéfinit explicitement l'ordre - utilisez-les pour modifier l'ordre du résultat

  • Commandé par
  • OrderByDescending
  • Inverser
  • Puis par
  • ThenByDescending

Redéfinit l'ordre selon certaines règles.

  • GroupBy - Les objets IGrouping sont générés dans un ordre basé sur l'ordre des éléments dans la source qui ont produit la première clé de chaque IGrouping. Les éléments d'un regroupement sont générés dans l'ordre dans lequel ils apparaissent dans la source.
  • GroupJoin - GroupJoin préserve l'ordre des éléments de l'extérieur, et pour chaque élément de l'extérieur, l'ordre des éléments correspondants de l'intérieur.
  • Join - préserve l'ordre des éléments de l'extérieur et, pour chacun de ces éléments, l'ordre des éléments correspondants de l'intérieur.
  • SelectMany - pour chaque élément de la source, le sélecteur est appelé et une séquence de valeurs est renvoyée.
  • Union - Lorsque l'objet renvoyé par cette méthode est énuméré, Union énumère les premier et deuxième dans cet ordre et renvoie chaque élément qui n'a pas déjà été généré.

Edit: J'ai déplacé Distinct à l'ordre de conservation basé sur cette implémentation .

    private static IEnumerable<TSource> DistinctIterator<TSource>
      (IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
    {
        Set<TSource> set = new Set<TSource>(comparer);
        foreach (TSource element in source)
            if (set.Add(element)) yield return element;
    }
Amy B
la source
2
En fait, je pense que Distinct conserve l'ordre d'origine (premier trouvé) - donc {1,2,1,3,1,3,4,1,5} serait {1,2,3,4,5}
Marc Gravell
10
msdn.microsoft.com/en-us/library/bb348436.aspx La méthode Distinct <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>))) renvoie une séquence non ordonnée qui ne contient aucune valeur en double .
Amy B
12
Marc: ce que vous dites pourrait être vrai, mais ce serait une mauvaise idée de se fier à ce comportement.
Amy B
4
@Amy B oui mais cela ne s'applique pas à Linq to Objects. Dans Linq to Sql, distinct () place le mot clé distinct dans le sql généré, et la commande à partir de sql n'est pas garantie. Je serais intéressé de voir une implémentation de distinct pour linq aux objets qui ne préserve pas l'ordre et est plus efficace que celui qui conserve l'ordre. Par exemple, vous pouvez consommer l'intégralité de l'entrée et la placer dans un hachage, puis produire des valeurs en énumérant le hachage (perte de l'ordre), mais c'est pire. Alors oui, ça ne me dérange pas de défier la documentation de temps en temps :)
dan
4
Peut-être que la documentation (pour la Distinctméthode) voulait juste dire "non trié", pas "dans un ordre imprévisible". Je dirais qu'il Distinctappartient à la catégorie de filtrage ci-dessus, tout comme Where.
Jeppe Stig Nielsen
34

Parlez-vous réellement de SQL ou de tableaux? En d'autres termes, utilisez-vous LINQ to SQL ou LINQ to Objects?

Les opérateurs LINQ to Objects ne changent pas réellement leur source de données d'origine - ils construisent des séquences qui sont efficacement soutenues par la source de données. Les seules opérations qui modifient l'ordre sont OrderBy / OrderByDescending / ThenBy / ThenByDescending - et même alors, celles-ci sont stables pour des éléments également ordonnés. Bien sûr, de nombreuses opérations filtreront certains éléments, mais les éléments renvoyés seront dans le même ordre.

Si vous convertissez vers une structure de données différente, par exemple avec ToLookup ou ToDictionary, je ne crois pas que l'ordre soit préservé à ce stade - mais c'est quelque peu différent de toute façon. (L'ordre des valeurs mappées à la même clé est cependant conservé pour les recherches, je crois.)

Jon Skeet
la source
donc parce que OrderBy est un tri stable, alors: seq.OrderBy (_ => _.Key) placera les éléments dans exactement le même ordre que seq.GroupBy (_ => _.Key) .SelectMany (_ => _ ). Est-ce exact?
dmg
1
@dmg: Non, ce ne sera pas le cas. Juste GroupBysuivi SelectManydonnera les résultats regroupés par clé, mais pas dans l'ordre croissant des clés ... il les donnera dans l'ordre dans lequel les clés se sont produites à l'origine.
Jon Skeet
dites-vous que LINQ to SQL ne préserve pas l'ordre?
symbionte
@symbiont: Dans de nombreuses opérations SQL, il n'y a pas d'ordre bien défini pour commencer. Fondamentalement, j'essaie de ne faire que des promesses sur des choses que je peux garantir - comme LINQ to Objects.
Jon Skeet
@JonSkeet Si j'utilise OrderBy, cela garantit-il que les objets 'n' avec la même clé conserveront leur séquence d'origine, sauf qu'ils sont tous ensemble. c'est-à-dire: list<x> {a b c d e f g}si c, d, e ont tous la même clé, alors la séquence résultante contiendra c, d, e côte à côte ET dans l'ordre c, d, e. Je n'arrive pas à trouver une réponse catégorique basée sur MS.
Paulustrious
7

Si vous travaillez sur un tableau, il semble que vous utilisez LINQ-to-Objects, pas SQL; pouvez-vous confirmer? La plupart des opérations LINQ ne réordonnent rien (la sortie sera dans le même ordre que l'entrée) - donc n'appliquez pas un autre tri (OrderBy [Descending] / ThenBy [Descending]).

[modifier: comme Jon l'a dit plus clairement; LINQ crée généralement une nouvelle séquence, laissant les données originales seules]

Notez que pousser les données dans un Dictionary<,>(ToDictionary) brouillera les données, car le dictionnaire ne respecte aucun ordre de tri particulier.

Mais les choses les plus courantes (sélectionner, où, sauter, prendre) devraient convenir.

Marc Gravell
la source
Si je ne me trompe pas, ToDictionary()ne fait simplement aucune promesse sur la commande, mais dans la pratique, maintient l'ordre d'entrée (jusqu'à ce que vous en supprimiez quelque chose). Je ne dis pas de me fier à cela, mais «brouiller» semble inexact.
Timo
4

J'ai trouvé une excellente réponse dans une question similaire qui fait référence à la documentation officielle. Pour le citer:

Pour les Enumerableméthodes (LINQ to Objects, qui applique List<T>), vous pouvez compter sur l'ordre des éléments retournés par Select, Whereou GroupBy. Ce n'est pas le cas pour les choses qui sont intrinsèquement non ordonnées comme ToDictionaryou Distinct.

De la documentation Enumerable.GroupBy :

Les IGrouping<TKey, TElement>objets sont livrés dans un ordre basé sur l'ordre des éléments en source qui ont produit la première clé de chacun IGrouping<TKey, TElement>. Les éléments d'un regroupement sont générés dans l'ordre dans lequel ils apparaissent source.

Ce n'est pas nécessairement vrai pour les IQueryableméthodes d'extension (autres fournisseurs LINQ).

Source: Les méthodes énumérables de LINQ maintiennent-elles l'ordre relatif des éléments?

Curtis Yallop
la source
2

Tout «groupé par» ou «commandé par» modifiera éventuellement la commande.

leppie
la source
0

La question ici se réfère spécifiquement à LINQ-to-Objects.

Si vous utilisez LINQ-to-SQL à la place, il n'y a pas d'ordre, sauf si vous en imposez un avec quelque chose comme:

mysqlresult.OrderBy(e=>e.SomeColumn)

Si vous ne le faites pas avec LINQ-to-SQL, l'ordre des résultats peut varier entre les requêtes suivantes, même des mêmes données, ce qui pourrait provoquer un bogue intermittent.

Andrew Pate
la source