[英]How to distinct a list using LINQ?
我有一个类Event
,它有两个属性:“ID”和“ExpirationTime”。 我有一个包含许多事件的列表,其中一些具有相同的ID。 我想创建一个有效的 LINQ查询,它将通过ID区分事件,并且对于每个ID,保持事件具有最小的ExpirationTime。
谢谢!
分组很简单,但使用标准的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));
简单! 你想把它们分组并从小组中挑出一个胜利者。
List<Event> distinctEvents = allEvents
.GroupBy(e => e.Id)
.Select(g => g.OrderBy(e => e.ExpirationTime).First())
.ToList();
我认为这应该优于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;
}
}
说明 :虽然这和Ani提出的GroupBy
方法具有相同的算法复杂度(据我所知,无论如何),上述方法在实践中更有效,原因有两个。
GroupBy
内部使用Lookup<TKey, TValue>
(非常类似于Dictionary<TKey, List<TValue>>
),它实际上使用输入序列的内容填充内部集合。 这需要更多的内存并且还会对性能产生影响,特别是由于虽然子集合将分摊 O(1)插入时间,但它们偶尔需要调整自身大小,这将是O(N)(其中N是子集合的大小)。 这不是什么大问题,但它仍然需要做很多工作。 GroupBy
提供枚举器之前迭代输入序列中的每个元素(因此它是延迟执行,但是在迭代GroupBy
的结果之前需要迭代整个输入序列) 。 然后,您在对Aggregate
的调用中再次遍历每个组; 总而言之,您在输入序列中迭代两次元素,这比完成手头任务所需的次数多一倍。 正如我所说,算法的复杂性是相同的,这意味着两种方法应该具有相同的可扩展性; 这个只是更快。 我冒昧地测试这两种方法(主要是出于好奇心),发现上述操作大约有一半的时间,并且比GroupBy
方法导致更少的GC集合(内存使用的粗略近似)。
这些是微小的问题,通常是浪费时间去思考太多。 我提到它们的唯一原因是你要求一个有效的解决方案(甚至加粗了这个术语); 所以我想你会想要考虑这些因素。
假设你可以在你的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);
}
}
我认为应该这样做:
events.GroupBy(x => x.ID, (key, items) => items.First(y => y.ExpirationTime == items.Min(z => z.ExpirationTime)))
将按ID分组,选择具有最小ExpirationTime
items
的事件(其中items
表示具有相同ID的所有事件)作为结果。
events.GroupBy(e => e.ID).Select(g => new { ID = g.Key, Time = g.Min(e => e.ExpirationTime) });
List<Event> events = null;
events
.GroupBy( e => e.ID )
.Select( g =>
g.First( e =>
e.ExpirationTime == g.Max( t =>
t.ExpirationTime
)
)
);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.