2011-02-09 67 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); 
    } 
}