2011-02-09 3 views
3

У меня есть класс Event которые имеют два свойства: «ID» и «ExpirationTime». У меня есть список, в котором много событий, некоторые из них с одинаковым идентификатором. Я хочу создать эффективный Запрос LINQ, который будет отличать события от идентификатора, и для каждого идентификатора сохранить событие с наименьшим значением ExpirationTime.Как отличить список с помощью LINQ?

Спасибо!

+0

'оставьте событие с наименьшим значением ExpirationTime?' Что вы имеете в виду? –

+0

Он имеет в виду держать ((французский adibe?) – Guillaume86

ответ

4

группировка достаточно легко, но делает эффективную «MinBy» со стандартным LINQ к объектам немного грязный:

var lowestByID = items.GroupBy(x => x.ID) 
         .Select(group => group.Aggregate((best, next) => 
            best.ExpirationTime < next.ExpirationTime 
            ? best : next)); 

Это пылесос с MinBy оператором, таким как поставляемый в комплекте с MoreLinq.

var lowestByID = items.GroupBy(x => x.ID) 
         .Select(group => group.MinBy(x => x.ExpirationTime)); 
+0

Наконец-то, правильный ответ! – LukeH

+0

+1 Это самый эффективный способ. –

+0

@ LukeH: Возможно, я ошибаюсь, но я думаю, что я взял общий трюк для 'O (n)' MinBy из одного из ваших * ответов. :) – Ani

1

Я думаю, что это нужно сделать, это:

events.GroupBy(x => x.ID, (key, items) => items.First(y => y.ExpirationTime == items.Min(z => z.ExpirationTime))) 

Уилла группу по ID, выбрав в результате событие в items (где items представляет все события с таким же идентификатором) с наименьшим ExpirationTime.

+0

он не будет отличаться, потому что 1) где производит IEnumerable, поэтому вам нужно сгладить SelectMany 2) Где может включать несколько событий, имеющих одинаковое значение ExpirationDate – Andrey

+2

Где (Min) is O (n^2) –

+0

Вы правы, но 'First' также должен работать. –

1
events.GroupBy(e => e.ID).Select(g => new { ID = g.Key, Time = g.Min(e => e.ExpirationTime) }); 
+2

Это не возвращает события. –

3

LINQ's Distinct() on a particular property

просто! Вы хотите сгруппировать их и выбрать победителя из группы.

List<Event> distinctEvents = allEvents 
    .GroupBy(e => e.Id) 
    .Select(g => g.OrderBy(e => e.ExpirationTime).First()) 
    .ToList(); 
+1

Ницца! Однако обратите внимание, что сортировка - o (nlogn), тогда как max - o (n) –

+0

@ohadsc. Вы правы. Я намеренно торгую небольшим количеством производительности для удобства использования/чтения. Также - можно было бы ожидать, что каждая группа будет немного меньше, чем общий список, поэтому эти мини-упорядочения быстрее, чем упорядочение всего списка. –

0
 List<Event> events = null; 
     events 
      .GroupBy(e => e.ID) 
      .Select(g => 
       g.First(e => 
        e.ExpirationTime == g.Max(t => 
         t.ExpirationTime 
        ) 
       ) 
      ); 
+0

Ницца, однако потребуется не более 2 проходов в списке, в отличие от макс, который требует 1 –

3

Я считаю, что это должно опережать GroupBy предложение (см краткое описание ниже):

IEnumerable<Event> DistinctEvents(IEnumerable<Event> events) 
{ 
    var dict = new Dictionary<int, Event>(); 

    foreach (Event e in events) 
    { 
     Event existing; 
     if (!dict.TryGetValue(e.Id, out existing) || e.ExpirationTime < existing.ExpirationTime) 
     { 
      dict[e.Id] = e; 
     } 
    } 

    foreach (Event e in dict.Values) 
    { 
     yield return e; 
    } 
} 

Объяснение: Хотя это и the GroupBy method proposed by Ani имеют одинаковую алгоритмической сложности (насколько я может сказать, во всяком случае), вышеупомянутый подход более эффективен на практике по двум причинам.

  1. GroupBy внутренне использует Lookup<TKey, TValue> (очень похож на Dictionary<TKey, List<TValue>>), который фактически заполняет внутренние коллекции с содержимым входной последовательности. Это требует большей памяти, а также имеет влияние на производительность, в частности, из-за того, что в то время, когда подкатегории будут иметь , амортизируются O (1) время вставки, они будут иногда нуждаться в изменении размера, что будет O (N) (где N - размер подсекции). Это не большое дело, но все еще намного больше работы, которую вы действительно делаете. нужен.
  2. Следствием точки # 1 является то, что это, в свою очередь, требует перебора каждого элемента в последовательности ввода доGroupBy может обеспечить перечислитель (так что это отложено исполнение, но потом весь входной последовательности должна быть итеративно до того итерации по результату GroupBy). Затем вы повторяете по каждой группе снова в звонок Aggregate; так что во всех случаях вы повторяете элементы во входной последовательности дважды, что больше времени, чем необходимо для выполнения поставленной задачи.

Как я уже сказал, алгоритмическая сложность одинакова, что означает, что два подхода должны быть одинаково масштабируемыми; это просто быстрее. Я взял на себя смелость тестировать оба подхода (из-за любопытства, в основном), и нашел выше, чтобы выполнить примерно в половине случаев и вызвать меньше коллекций GC (приблизительное приближение использования памяти), чем подход GroupBy.

Это минутные проблемы, которые обычно представляют собой пустую трату времени, чтобы слишком много думать. Единственная причина, по которой я упоминаю их, заключается в том, что вы попросили эффективное решение (и даже выделено жирным шрифтом); поэтому я решил, что вы захотите принять во внимание эти факторы.

+0

+1 Ницца, это много усилий; бенчмаркинг и все. (Это одна из проблем с «конвейером информации» в LINQ to Objects, операторы не имеют больших изображений, поэтому весь запрос не может быть оптимизирован на этой основе) – Ani

+0

@Ani: Да, и быть справедливым Я вижу, что OP * * специально запрашивал «запрос LINQ»; мой ответ не соответствует этому описанию. Я всегда нахожу, что это немного странно, хотя разработчики стремятся найти наиболее «эффективное» решение проблемы и добавить требование, чтобы он использовал LINQ (вроде как «Я хочу лучший инструмент для этой работы и этот инструмент должен быть молотком »). Что касается бенчмаркинга, то это то, что я делаю так часто, у меня просто есть небольшой проект песочницы со всеми инструментами бенчмаркинга; по сути, я поплю в делегатах и ​​вижу, как они выполняют над кучей итераций. –

+0

@Ani: ... который не должен сказать, что я * не * трачу слишком много времени на СО (я, очевидно, делаю)! –

2

Предполагая, что вы можете реализовать IComparable на вашем Event класса (с LINQ-х Min не имеет перегрузки, возвращая исходный элемент в противном случае), вы можете сделать:

var distinct = events.GroupBy(evt => evt.Id).Select(grp => grp.Min()); 

Пример:

void Main() 
{ 
    var events = new List<Event> 
    { 
     new Event(1, DateTime.Now), 
     new Event(1, DateTime.Now.AddDays(1)), 
     new Event(2, DateTime.Now.AddDays(2)), 
     new Event(2, DateTime.Now.AddDays(-22)), 
    }; 

    var distinct = events.GroupBy(evt => evt.Id).Select(grp => grp.Min()); 
} 

public class Event : IComparable<Event> 
{ 
    public Event(int id, DateTime exp) 
    { 
     Id = id; 
     Expiration = exp; 
    } 
    public int Id {get; set;} 
    public DateTime Expiration {get; set;} 

    public int CompareTo(Event other) 
    { 
     return Expiration.CompareTo(other.Expiration); 
    } 
} 
+0

Использование Min этот путь довольно круто. +1 –

Смежные вопросы