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

时间:2015-05-26 13:15:40

标签: c# linq

我有一个事件列表,现在我想找出哪些事件重叠。您可以在下面找到我目前拥有的代码,但我遇到的问题是,搜索到的项目也包含在列表中。

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()应该可以实现。

修改

我创建了一个dotnetfiddle,以便可以使用它。一个重要的问题是重叠算法。

活动1:
StartDate:今天,10:00
EndDate:今天,10:05

活动2:
StartDate:今天,10:05
EndDate:今天,10:10

如果您向用户提供此信息,则不会重叠。所以我要修改我的算法。

5 个答案:

答案 0 :(得分:3)

我会这样做:

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>。该数组包含重叠事件。

答案 1 :(得分:2)

我会尝试这样的事情:

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))

对于非重叠,您只需使用EventListExcept过滤重叠事件:

var nonOverlapping = EventList.Except(overlappingEvents);

我不知道您的SomeEventObject,但您可能需要通过一些Id比较来更改不等式比较。

您可能需要查看quite good Q&A on detecting overlapping periods in C#。为方便起见,您还可以将检查分解如下:

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

答案 2 :(得分:1)

你可以用这个得到结果:

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

答案 3 :(得分:1)

我知道你说你想要使用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

答案 4 :(得分:0)

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