如何使用LINQ区分列表?

时间:2011-02-09 15:38:35

标签: c# linq distinct

我有一个类Event,它有两个属性:“ID”和“ExpirationTime”。 我有一个包含许多事件的列表,其中一些具有相同的ID。 我想创建一个高效的 LINQ查询,该查询将通过ID区分事件,并且对于每个ID,使事件保持最小的ExpirationTime。

谢谢!

7 个答案:

答案 0 :(得分:4)

分组很简单,但使用标准的LINQ to Objects执行高效的“MinBy”有点麻烦:

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 :(得分: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(); 

答案 2 :(得分: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的调用中重复遍历每个;所以总的来说,你要迭代输入序列两次中的元素,这比完成手头任务所需的次数要多。
  3. 正如我所说,算法的复杂性是相同的,这意味着两种方法应该具有相同的可扩展性;这个只是更快。我冒昧地测试这两种方法(主要是出于好奇心),并发现上述内容大约在一半的时间内执行,导致GC集合(内存使用的粗略近似)比GroupBy方法更少。

    这些都是微不足道的问题,通常浪费时间去思考太多。我提到它们的唯一原因是你要求一个高效解决方案(甚至加粗了这个术语);所以我想你会考虑这些因素。

答案 3 :(得分: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);
    }
}

答案 4 :(得分:1)

我认为应该这样做:

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

将按ID进行分组,选择items中的事件(其中items表示具有相同ID的所有事件),其中ExpirationTime为最小的事件。

答案 5 :(得分:1)

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

答案 6 :(得分:0)

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