Ordenar una lista de otras ID de lista

150

Tengo una lista con algunos identificadores como este:

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

Además, tengo otra lista de <T>elementos, que están representados por los identificadores descritos anteriormente.

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

Necesito mantener el mismo orden en ambas colecciones, de modo que los elementos en List<T>deben estar en la misma posición que en la primera (debido a razones de puntuación del motor de búsqueda). Y este proceso no se puede hacer en la GetDocsFromDb()función.

Si es necesario, es posible cambiar la segunda lista a otra estructura ( Dictionary<long, T>por ejemplo), pero preferiría no cambiarla.

¿Hay alguna forma simple y eficiente de hacer esta "orden según algunas ID" con LINQ?

Borja López
fuente
¿está seguro de que todo docIdocurre exactamente una vez docs, qué propiedad contendrá Ido se requerirá un selector Func<T, long>?
Jodrell
¿La primera lista representa una "lista maestra"? En otras palabras, ¿la segunda lista será un subconjunto que representa una parte (o la totalidad) de la primera lista?
code4life

Respuestas:

332
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();
Denys Denysenko
fuente
@Kaf es por eso que también voté, se basa en saber que se llama a la propiedad de ID de documento Id. No se especifica en la pregunta.
Jodrell
3
@ BorjaLópez, una nota rápida. Menciona eficiencia en su pregunta. IndexOfes perfectamente aceptable para su ejemplo y agradable y simple. Si tuviera muchos datos, mi respuesta podría ser más adecuada. stackoverflow.com/questions/3663014/…
Jodrell
2
@DenysDenysenko Fantástico. Muchas gracias; Exactamente lo que estaba buscando.
silkfire
3
no funciona si tiene elementos en documentos que no tienen identificadores en la lista de pedidos
Dan Hunex
44
Bastante ineficiente: se llama a IndexOf para cada elemento de la colección de origen y OrderBy tiene que ordenar los elementos. La solución de @Jodrell es mucho más rápida.
sdds
25

Como no especificas 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];
    }
}

Es una extensión genérica para lo que quieres.

Podrías usar la extensión como esta quizás,

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

Una versión más segura podría 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á si sourceno se comprime exactamente con order.

Jodrell
fuente
Usé esta solución y funcionó. Solo eso, tuve que hacer el método estático y la clase estática.
Vinmm
5

La respuesta de Jodrell es la mejor, pero en realidad se volvió a implementar System.Linq.Enumerable.Join. Join también usa Lookup y sigue ordenando la fuente.

    docIds.Join(
      docs,
      i => i,
      d => d.Id,
      (i, d) => d);
Kladzey
fuente
Esa es la respuesta que estamos buscando
Orace
2
Esto solo prueba que Join es demasiado difícil de entender ya que todos acordaron que reescribirlo fue más fácil.
PRMan
-3

Un enfoque simple es comprimir con la secuencia de pedido:

List<T> docs = GetDocsFromDb(...).Zip(docIds, Tuple.Create)
               .OrderBy(x => x.Item2).Select(x => x.Item1).ToList();
Albin Sunnanbo
fuente
¿Por qué ordenar después de un zip?
Jodrell
Porque Zipcombina cada índice (en una Tupla) con el documento en la misma posición en la lista correspondiente. Luego, OrderBy clasifica las Tuplas por la parte del índice y luego la selección excava nuestros documentos de la lista ahora ordenada.
Albin Sunnanbo
pero, el resultado de GetDocsFromDb no está ordenado, por lo que creará Tuplas donde Item1no está relacionado Item2.
Jodrell
1
Creo que esto producirá resultados incorrectos desde que ordené realizar la identificación de uppon y no el índice.
Michael Logutov