2011-02-09 83 views
3

我有一個類Event有兩個屬性:「ID」和「ExpirationTime」。 我有一個列表有許多事件,其中一些具有相同的ID。 我想創建有效的 LINQ查詢,將通過ID區分事件,併爲每個ID保持具有最小ExpirationTime的事件。如何使用LINQ來區分列表?

謝謝!

+0

`用最小的ExpirationTime離開事件?`你是什麼意思? – 2011-02-09 15:44:20

+0

他指保持,(法國adibe?) – Guillaume86 2011-02-09 15:46:56

回答

4

分組是很容易的,但是做了有效的「MinBy」與標準的LINQ to Objects是略顯凌亂:

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)); 
1

我想這應該這樣做:

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

威爾集團通過ID,在items選擇結果是該事件(其中items代表所有具有相同ID的事件)與最小ExpirationTime

+0

也不會顯着,因爲:1)如果產生了IEnumerable,所以你必須通過的SelectMany 2)在哪裏可以包括幾個事件具有相同的到期日期 – Andrey 2011-02-09 15:51:02

+2

拉平凡(最小值)爲O(n^2) – 2011-02-09 16:00:15

+0

你是對的,但`First`也應該有效。 – 2011-02-09 16:01:24

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

這不會返回事件。 – 2011-02-09 15:59:17

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),而最大值是o(n) – 2011-02-09 15:53:55

+0

@ohadsc您是對的。爲了便於使用/閱讀,我故意爲了一點點的表現而交易。另外 - 人們會期望每個組都比整個列表小很多,所以這些小型排序比排序整個列表要快。 – 2011-02-09 15:56:21

0
 List<Event> events = null; 
     events 
      .GroupBy(e => e.ID) 
      .Select(g => 
       g.First(e => 
        e.ExpirationTime == g.Max(t => 
         t.ExpirationTime 
        ) 
       ) 
      ); 
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收集(大致近似存儲器使用)。

這些擔憂分鐘,它通常會的時間想太多的浪費。我提到他們的唯一原因是,你問一個高效溶液(甚至加粗術語);所以我想你會想把這些因素考慮進去。

2

假設你可以在你的Event類實現IComparable(因爲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); 
    } 
}