Trier une liste à partir d'autres ID de liste

150

J'ai une liste avec des identifiants comme celui-ci:

List<long> docIds = new List<long>() { 6, 1, 4, 7, 2 };

Morover, j'ai une autre liste d' <T>éléments, qui sont représentés par les identifiants décrits ci-dessus.

List<T> docs = GetDocsFromDb(...)

Je dois garder le même ordre dans les deux collections, de sorte que les éléments dans List<T>doivent être dans la même position que dans la première (pour des raisons de notation des moteurs de recherche). Et ce processus ne peut pas être effectué dans la GetDocsFromDb()fonction.

Si nécessaire, il est possible de changer la deuxième liste en une autre structure ( Dictionary<long, T>par exemple), mais je préfère ne pas la changer.

Existe-t-il un moyen simple et efficace de faire cette «classification en fonction de certains ID» avec LINQ?

Borja López
la source
êtes-vous assuré que tout docIdse produit exactement une fois docs, quelle propriété tiendra le Idou un sélecteur Func<T, long>sera-t-il nécessaire?
Jodrell
La première liste représente-t-elle une «liste maîtresse»? Un autre mot, la deuxième liste sera-t-elle un sous-ensemble représentant une partie (ou l'intégralité) de la première liste?
code4life

Réponses:

332
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();
Denys Denysenko
la source
@Kaf c'est pourquoi j'ai voté pour moi aussi, repose sur le fait de savoir que la propriété ID de document est appelée Id. Ce n'est pas spécifié dans la question.
Jodrell
3
@ BorjaLópez, un petit mot. Vous parlez d'efficacité dans votre question. IndexOfest parfaitement acceptable pour votre exemple et agréable et simple. Si vous aviez beaucoup de données, ma réponse serait peut-être mieux adaptée. stackoverflow.com/questions/3663014/…
Jodrell
2
@DenysDenysenko Fantastique. Merci beaucoup; exactement ce que je cherchais.
silkfire
3
ne fonctionne pas si vous avez des éléments dans les documents qui n'ont pas d'identifiant dans la liste de commande
Dan Hunex
4
Assez inefficace - IndexOf est appelé pour chaque élément de la collection source et OrderBy doit ordonner les éléments. La solution de @Jodrell est beaucoup plus rapide.
sdds
25

Puisque vous ne spécifiez pas T,

IEnumerable<T> OrderBySequence<T, TId>(
       this IEnumerable<T> source,
       IEnumerable<TId> order,
       Func<T, TId> idSelector)
{
    var lookup = source.ToDictionary(idSelector, t => t);
    foreach (var id in order)
    {
        yield return lookup[id];
    }
}

Est une extension générique pour ce que vous voulez.

Vous pouvez peut-être utiliser l'extension comme celle-ci,

var orderDocs = docs.OrderBySequence(docIds, doc => doc.Id);

Une version plus sûre pourrait être

IEnumerable<T> OrderBySequence<T, TId>(
       this IEnumerable<T> source,
       IEnumerable<TId> order,
       Func<T, TId> idSelector)
{
    var lookup = source.ToLookup(idSelector, t => t);
    foreach (var id in order)
    {
        foreach (var t in lookup[id])
        {
           yield return t;
        }
    }
}

qui fonctionnera si sourcene ferme pas exactement avec order.

Jodrell
la source
J'ai utilisé cette solution et cela a fonctionné. Juste ça, j'ai dû rendre la méthode statique et la classe statique.
vinmm
5

La réponse de Jodrell est la meilleure, mais en fait, il a réimplémenté System.Linq.Enumerable.Join. Join utilise également Lookup et continue de classer la source.

    docIds.Join(
      docs,
      i => i,
      d => d.Id,
      (i, d) => d);
Kladzey
la source
C'est la réponse que nous recherchons
Orace le
2
Cela prouve simplement que Join est trop difficile à comprendre puisque tout le monde a convenu que la réécriture était plus facile.
PRMan
-3

Une approche simple consiste à compresser avec la séquence de commande:

List<T> docs = GetDocsFromDb(...).Zip(docIds, Tuple.Create)
               .OrderBy(x => x.Item2).Select(x => x.Item1).ToList();
Albin Sunnanbo
la source
pourquoi commander après un zip?
Jodrell
Parce que Zipcombine chaque index (dans un tuple) avec le document à la même position dans la liste correspondante. Ensuite, OrderBy trie les tuples par la partie d'index, puis la sélection ne creuse que les documents de la liste maintenant ordonnée.
Albin Sunnanbo
mais, le résultat de GetDocsFromDb n'est pas ordonné, vous allez donc créer des tuples là où il Item1n'y a aucun rapport Item2.
Jodrell
1
Je pense que cela produira des résultats incorrects depuis la commande exécutant uppon id et non l'index.
Michael Logutov