使用LINQ查找重叠的时间段(事件)
本文关键字:事件 时间段 LINQ 查找 重叠 使用 | 更新日期: 2023-09-27 17:50:52
我有一个事件列表,现在我想找出哪些事件重叠。下面您可以找到我目前拥有的代码,但我有一个问题,即搜索的项目也包含在列表中。
List<SomeEventObject> overlappingEvents = new List<SomeEventObject>();
foreach (SomeEventObject eventItem in EventList)
{
bool overlapping = false;
foreach (SomeEventObject anotherEventItem in EventList)
{
if (eventItem.StartDate <= anotherEventItem.EndDate &&
eventItem.EndDate >= anotherEventItem.StartDate)
{
overlapping = true;
overlappingEvents.Add(anotherEventItem);
}
}
if (overlapping)
overlappingEvents.Add(eventItem);
}
我需要创建一个没有搜索项的新列表。因此,我问是否有一个很好的LINQ表达式,可以为我处理。这是我想到的一些伪代码:
EventList.Where(e =>
eventItem.StartDate <= e.EndDate &&
eventItem.EndDate >= e.StartDate);
在这个上下文中eventItem当然不存在。
因此,我认为我需要两个列表:一个具有重叠事件,另一个具有非重叠事件。但如果我有重叠事件列表,这应该是可能的.Except()
。
我已经创建了一个。netfiddle,以便大家可以玩它。一个重要的问题是重叠算法。
事件1:
开始日期:今天,10:00
结束日期:今天,10:05
开始日期:今天,10:05
结束日期:今天,10:10
如果你把这个呈现给用户,那么这不是重叠。所以我要修改我的算法
我会这样做:
var overlappingEvents =
(
from e1 in EventList
from e2 in EventList
where e1 != e2
where e1.StartDate <= e2.EndDate
where e1.EndDate >= e2.StartDate
from e in new [] { e1, e2 }
select e
).ToList();
我认为这应该是相当直接的。
根据评论,这个版本只返回一个EventList
元素一次,不管它参与了多少个重叠。
var overlappingEvents =
(
from e1 in EventList
where EventList
.Where(e2 => e1 != e2)
.Where(e2 => e1.StartDate <= e2.EndDate)
.Where(e2 => e1.EndDate >= e2.StartDate)
.Any()
select e1
).ToList();
也可以写成:
var overlappingEvents =
EventList
.Where(e1 =>
EventList
.Where(e2 => e1 != e2)
.Where(e2 => e1.StartDate <= e2.EndDate)
.Where(e2 => e1.EndDate >= e2.StartDate)
.Any())
.ToList();
根据进一步的注释,下面是如何配对事件:
var overlappingEvents =
(
from e1 in EventList
from e2 in EventList
where e1 != e2
where e1.StartDate <= e2.EndDate
where e1.EndDate >= e2.StartDate
select new [] { e1, e2 }
).ToList();
现在overlappingEvents
是一个数组列表- List<SomeEventObject[]>
而不是List<SomeEventObject>
。数组包含重叠的事件
我会尝试这样做:
var searchedFor = EventList.First(); // Replace by the proper one
var overlapping = EventList.Where(e => e != searchedFor &&
EventList.Any(ev => e != ev && ev.StartDate <= e.EndDate && ev.EndDate >= e.StartDate))
// Alternatively with Except
var overlapping = EventList.Except(new[] { searchFor }).Where(e =>
EventList.Any(ev => e != ev && ev.StartDate <= e.EndDate && ev.EndDate >= e.StartDate))
对于非重叠事件,您只需使用Except
:
EventList
中过滤重叠事件。var nonOverlapping = EventList.Except(overlappingEvents);
我不知道你的SomeEventObject
,但你可能需要通过一些Id
比较来改变不等式比较。
关于在c#中检测重叠周期,有一个非常好的问题& a,你可能想看看。为方便起见,您还可以将支票按如下方式分解:
Func<SomeEventObject, SomeEventObject, bool> areOverlapped = (e1, e2) =>
e1.StartDate <= e2.EndDate && e1.EndDate >= e2.StartDate;
var overlapping = EventList.Where(e => e != searchedFor &&
EventList.Any(ev => e != ev && areOverlapped(e, ev)));
你可以这样得到结果:
var nonOverlappingEvents = EventList.Where(e1 => !EventList.Where(e2 => e2 != e1).Any(e2 => e1.StartDate <= e2.EndDate && e1.EndDate >= e2.StartDate));
但是我不明白你的重叠算法。我认为重叠是指一个事件的StartDate
在另一个事件的StartDate
和EndDate
之间:
var nonOverlappingEvents = EventList.Where(e1 => !EventList.Where(e2 => e2 != e1).Any(e2 => e2.StartDate >= e1.StartDate && e2.StartDate <= e1.EndDate));
我知道你说你想使用Linq,它当然是相当直接的。我们过去是这样做的,但是我们做了很多日期范围的工作,发现了一个更好的方法。
如果你做了很多这样的工作,我建议你使用Itenso time Period Library (Nuget上有)。
它支持非常丰富的基于时间的操作。如IsSamePeriod、HasInside、OverlapsWith、IntersectsWith等可用于周期关系。
它还支持时间集合,例如一个人的所有事件。它们基于ITimePeriodCollection
,可以保存ITimePeriod
类型的任意元素,并将其所有元素的最早开始解释为收集时间段的开始。相应的,其所有元素的最后结束作为收集周期的结束。
一些代码示例来了解它是如何工作的
//Setup a time range
TimeRange timeRange1 = new TimeRange(
new DateTime( 2011, 2, 22, 14, 0, 0 ),
new DateTime( 2011, 2, 22, 18, 0, 0 ) );
Console.WriteLine( "TimeRange1: " + timeRange1 );
// > TimeRange1: 22.02.2011 14:00:00 - 18:00:00 | 04:00:00
// --- Setu uptime range 2 ---
TimeRange timeRange2 = new TimeRange(
new DateTime( 2011, 2, 22, 15, 0, 0 ),
new TimeSpan( 2, 0, 0 ) );
Console.WriteLine( "TimeRange2: " + timeRange2 );
// > TimeRange2: 22.02.2011 15:00:00 - 17:00:00 | 02:00:00
// --- relation ---
Console.WriteLine( "TimeRange1.GetRelation( TimeRange2 ): " +
timeRange1.GetRelation( timeRange2 ) );
// --- intersection ---
Console.WriteLine( "TimeRange1.GetIntersection( TimeRange2 ): " +
timeRange1.GetIntersection( timeRange2 ) );
// > TimeRange1.GetIntersection( TimeRange2 ):
// 22.02.2011 15:00:00 - 17:00:00 | 02:00:00
不使用linq,而是将重叠的句号组为集合:
public class Range
{
public DateTime Start { get; set; }
public DateTime Stop { get; set; }
public Range(DateTime start, DateTime stop)
{
Start = start;
Stop = stop;
}
}
void Main()
{
List<Range> ranges = new List<Range>();
ranges.Add(new Range(new DateTime(2019, 10, 1), new DateTime(2019, 10, 2)));
ranges.Add(new Range(new DateTime(2019, 10, 2), new DateTime(2019, 10, 3)));
ranges.Add(new Range(new DateTime(2019, 10, 1), new DateTime(2019, 10, 3)));
ranges.Add(new Range(new DateTime(2019, 10, 4), new DateTime(2019, 10, 5)));
ranges.Add(new Range(new DateTime(2019, 10, 6), new DateTime(2019, 10, 8)));
ranges.Add(new Range(new DateTime(2019, 10, 5), new DateTime(2019, 10, 7)));
ranges.Add(new Range(new DateTime(2019, 10, 9), new DateTime(2019, 10, 9)));
var rangesOrdered = ranges.OrderBy(t => t.Start).ToList();
var sets = new List<List<Range>>() { new List<Range>() {rangesOrdered[0]}};
var IsOverlaping = new Func<Range, Range, bool>((a, b) => a.Start < b.Stop && b.Start < a.Stop);
for (var i = 1; i < rangesOrdered.Count; i++)
{
if (IsOverlaping(sets.Last().OrderBy(s => s.Stop).Last(), rangesOrdered[i]))
{
sets.Last().Add(rangesOrdered[i]);
}
else
{
sets.Add(new List<Range>() {rangesOrdered[i]});
}
}
}