Classifique uma lista de outros IDs de lista

150

Eu tenho uma lista com alguns identificadores como este:

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

Além disso, tenho outra lista de <T>itens, representados pelos IDs descritos acima.

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

Preciso manter a mesma ordem nas duas coleções, para que os itens List<T>estejam na mesma posição que na primeira (devido a razões de pontuação do mecanismo de pesquisa). E esse processo não pode ser feito na GetDocsFromDb()função.

Se necessário, é possível alterar a segunda lista para outra estrutura ( Dictionary<long, T>por exemplo), mas eu prefiro não alterá-la.

Existe alguma maneira simples e eficiente de fazer essa "ordenação dependendo de alguns IDs" com o LINQ?

Borja López
fonte
você tem certeza de que tudo docIdocorre exatamente uma vez docs, qual propriedade manterá o Idseletor ou será Func<T, long>necessário?
precisa
A primeira lista representa uma "lista principal"? Por outras palavras, a segunda lista será um subconjunto que representa uma parte (ou a totalidade) da primeira lista?
code4life

Respostas:

332
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();
Denys Denysenko
fonte
@Kaf é por isso que votei também, depende de saber que a propriedade ID do documento é chamada Id. Não está especificado na pergunta.
Jodrell
3
@ BorjaLópez, uma nota rápida. Você mencionou eficiência em sua pergunta. IndexOfé perfeitamente aceitável para o seu exemplo e agradável e simples. Se você tivesse muitos dados, minha resposta poderia ser mais adequada. stackoverflow.com/questions/3663014/…
Jodrell 17/07
2
@DenysDenysenko Fantastic. Muito obrigado; Exatamente o que eu estava procurando.
Silkfire
3
não funciona se você tiver elementos em documentos que não têm ids na lista de ordenação
Dan Hunex
4
Bastante ineficiente - IndexOf está sendo chamado para cada elemento na coleção de origem e OrderBy precisa ordenar os elementos. A solução da @Jodrell é muito mais rápida.
Sdds 29/11
25

Como você não especifica 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];
    }
}

É uma extensão genérica para o que você deseja.

Talvez você possa usar a extensão assim,

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

Uma versão mais segura pode ser

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

que funcionará se sourcenão for exatamente o mesmo order.

Jodrell
fonte
Eu usei esta solução e funcionou. Apenas isso, eu tive que tornar o método estático e a classe estática.
vinmm
5

A resposta de Jodrell é a melhor, mas na verdade ele foi reimplementado System.Linq.Enumerable.Join. A associação também usa a Pesquisa e mantém a ordem de origem.

    docIds.Join(
      docs,
      i => i,
      d => d.Id,
      (i, d) => d);
Kladzey
fonte
Essa é a resposta que estamos procurando
Orace 27/01
2
Isso apenas prova que o Join é muito difícil de entender, pois todos concordaram que reescrevê-lo era mais fácil.
PRMan 16/04
-3

Uma abordagem simples é compactar com a sequência de pedidos:

List<T> docs = GetDocsFromDb(...).Zip(docIds, Tuple.Create)
               .OrderBy(x => x.Item2).Select(x => x.Item1).ToList();
Albin Sunnanbo
fonte
por que encomendar depois de um zip?
precisa
Porque Zipcombina cada índice (em uma Tupla) com o documento na mesma posição na lista correspondente. Em seguida, o OrderBy classifica as Tuplas pela parte do índice e, em seguida, o select escava nossos documentos apenas da lista ordenada agora.
Albin Sunnanbo 7/13
mas o resultado de GetDocsFromDb não é ordenado, portanto você criará Tuplas onde Item1não estiver relacionado Item2.
precisa
1
Eu acho que isso produzirá resultados incorretos desde que você solicitou a realização de uppon id e não o índice.
Michael Logutov 31/03