Сортировать список из другого списка идентификаторов

150

У меня есть список с некоторыми идентификаторами, как это:

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

Более того, у меня есть еще один список <T>предметов, которые представлены идентификаторами, описанными выше.

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

Мне нужно сохранить одинаковый порядок в обеих коллекциях, чтобы элементы в List<T> должны быть в том же положении, что и в первом (из-за причин оценки поисковой системой). И этот процесс не может быть выполнен в GetDocsFromDb()функции.

При необходимости можно изменить второй список в другую структуру (Dictionary<long, T> например, ), но я бы предпочел не менять его.

Есть ли простой и эффективный способ сделать это «расположение в зависимости от некоторых идентификаторов» с помощью LINQ?

Борха Лопес
источник
Вы уверены, что все docIdпроисходит ровно один раз docs, какое свойство будет содержать Idили потребуется селектор Func<T, long>?
Джодрелл
Первый список представляет собой «основной список»? Другими словами, будет ли второй список подмножеством, представляющим часть (или всю) первого списка?
code4life

Ответы:

332
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();
Денис Денисенко
источник
@Kaf, поэтому я тоже проголосовал, полагается, что свойство идентификатора документа вызывается Id. Это не указано в вопросе.
Джодрелл
3
@ BorjaLópez, короткая заметка. Вы упоминаете эффективность в своем вопросе. IndexOfвполне приемлемо для вашего примера и красиво и просто. Если у вас много данных, мой ответ может быть лучше. stackoverflow.com/questions/3663014/…
Джодрелл
2
@DenysDenysenko Фантастика. Спасибо; именно то, что я искал.
шелковый пожар
3
не работает, если у вас есть элементы в документах, у которых нет идентификаторов в списке упорядочивания
Dan Hunex
4
Совершенно неэффективно - IndexOf вызывается для каждого элемента в исходной коллекции, и OrderBy должен упорядочивать элементы. Решение @Jodrell намного быстрее.
ССРД ,
25

Поскольку вы не указываете 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];
    }
}

Является общим расширением для того, что вы хотите.

Вы можете использовать расширение, как это возможно,

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

Более безопасная версия может быть

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

который будет работать, если sourceне работает с zip order.

Jodrell
источник
Я использовал это решение, и оно сработало. Просто я должен был сделать метод статическим, а класс - статическим.
Винм
5

Ответ Джодрелла лучший, но на самом деле он переопределен System.Linq.Enumerable.Join. Join также использует Lookup и сохраняет порядок источника.

    docIds.Join(
      docs,
      i => i,
      d => d.Id,
      (i, d) => d);
Kladzey
источник
Вот ответ, который мы ищем
Orace
2
Это просто доказывает, что Join слишком сложен для понимания, поскольку все согласились с тем, что переписать его было проще.
PRMan
-3

Один простой подход заключается в том, чтобы выполнить последовательность заказа:

List<T> docs = GetDocsFromDb(...).Zip(docIds, Tuple.Create)
               .OrderBy(x => x.Item2).Select(x => x.Item1).ToList();
Альбин Суннанбо
источник
почему заказ после почтового индекса?
Джодрелл
Потому что Zipобъединяет каждый индекс (в кортеж) с документом в той же позиции в соответствующем списке. Затем OrderBy сортирует кортежи по индексной части, а затем селектор выбирает только наши документы из упорядоченного списка.
Альбин Суннанбо
но результат GetDocsFromDb неупорядочен, поэтому вы будете создавать кортежи там, где они Item1не связаны Item2.
Джодрелл
1
Я думаю, что это приведет к неправильным результатам, так как порядок выполнения идентификатора Uppon, а не индекса.
Михаил Логутов