使用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

事件2:


开始日期:今天,10:05
结束日期:今天,10:10

如果你把这个呈现给用户,那么这不是重叠。所以我要修改我的算法

使用LINQ查找重叠的时间段(事件)

我会这样做:

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在另一个事件的StartDateEndDate之间:

    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]});
            }
    }
}