找到重叠的时间间隔并将它们分割成新的时间间隔

Find overlapping time intervals and slice them into new ones

我需要一些帮助来在 C# 中构建一个算法,该算法采用时间间隔列表(开始、结束),找到任何重叠的时间段并在 start/end 处剪切它们。然后需要将它以“块”的形式合并在一起,这样最终的时间间隔就是一个周期。 我会用图来说明。

到目前为止我的代码。它适用于两个时间间隔但引入三分之一或更多,它开始给出奇怪的结果:)

// Dummy objects
        var d1 = new TimeSlot(DateTime.Parse("2020-05-05 13:00 PM"), DateTime.Parse("2020-05-05 13:30 PM"), "1");
        var d3 = new TimeSlot(DateTime.Parse("2020-05-05 13:15 PM"), DateTime.Parse("2020-05-05 13:25 PM"), "2");
        var d2 = new TimeSlot(DateTime.Parse("2020-05-05 13:05 PM"), DateTime.Parse("2020-05-05 13:20 PM"), "3");

        List<TimeSlot> dates = new List<TimeSlot>();
        dates.Add(d1);
        dates.Add(d2);
        dates.Add(d3);

        List<TimeSlot> slicedDates = new List<TimeSlot>();

        IEnumerable<TimeSlot> dateContainer = dates;
        TimeSlot prev = dateContainer.First();
        dateContainer = dateContainer.Skip(1);

        foreach (TimeSlot date in dateContainer.OrderBy(x => x.StartDate))
        {
            var prevStartTime = prev.StartDate;
            var prevEndTime = prev.EndDate;

            if (date.StartDate < prev.EndDate)
            {
                TimeSlot leftSlice = new TimeSlot(prevStartTime, date.StartDate, prev.Name);
                slicedDates.Add(leftSlice);
            }

            if (date.EndDate < prevEndTime)
            {
                TimeSlot middleSlice = new TimeSlot(date.StartDate, date.EndDate, prev.Name + "," + date.Name);
                slicedDates.Add(middleSlice);

                TimeSlot rightSlice = new TimeSlot(date.EndDate, prevEndTime, prev.Name);
                slicedDates.Add(rightSlice);
            }

            prev = date;
        }

并且输出三个错误的时间间隔:

05-05-2020 13:00:00 => 05-05-2020 13:05:00: 1
05-05-2020 13:05:00 => 05-05-2020 13:20:00: 1,3
05-05-2020 13:05:00 => 05-05-2020 13:15:00: 3
05-05-2020 13:20:00 => 05-05-2020 13:30:00: 1

我稍微调整了一下算法:

        var d1 = new TimeSlot(DateTime.Parse("2020-05-05 13:00 PM"), DateTime.Parse("2020-05-05 13:30 PM"), "1");
        var d3 = new TimeSlot(DateTime.Parse("2020-05-05 13:15 PM"), DateTime.Parse("2020-05-05 13:25 PM"), "2");
        var d2 = new TimeSlot(DateTime.Parse("2020-05-05 13:05 PM"), DateTime.Parse("2020-05-05 13:20 PM"), "3");

        List<TimeSlot> dates = new List<TimeSlot>();
        dates.Add(d1);
        dates.Add(d2);
        dates.Add(d3);

        List<TimeSlot> slicedDates = new List<TimeSlot>();

        IEnumerable<TimeSlot> dateContainer = dates;

        // Created an ordered list of Start & End dates.
        var times = dateContainer.Select(x => x.StartDate);
        times = times.Concat(dateContainer.Select(x => x.EndDate));
        var orderedTimes = times.Distinct().OrderBy(x => x);
        var prev = orderedTimes.First();
        times = orderedTimes.Skip(1);

        foreach (var time in times)
        {
            var names = new List<string>();
            foreach (TimeSlot date in dateContainer)
            {
                // Add the TimeSlot if it's in range
                if (prev >= date.StartDate && time <= date.EndDate)
                {
                    names.Add(date.Name);
                }
            }

            var name = string.Join(",",names);
            TimeSlot slot = new TimeSlot(prev, time, name);
            slicedDates.Add(slot);

            prev = time;
        }

还有一个可以玩:

List<TimeSlot> originalSlots = new List<TimeSlot>() {
    new TimeSlot(DateTime.Parse("2020-05-05 13:00 PM"), DateTime.Parse("2020-05-05 13:30 PM"), "1"),
    new TimeSlot(DateTime.Parse("2020-05-05 13:15 PM"), DateTime.Parse("2020-05-05 13:25 PM"), "2"),
    new TimeSlot(DateTime.Parse("2020-05-05 13:05 PM"), DateTime.Parse("2020-05-05 13:20 PM"), "3")
};

var times = originalSlots.Select(ts => ts.StartDate)
    .Concat(originalSlots.Select(ts => ts.EndDate))
    .Distinct().OrderBy(dt => dt);
var slicedSlots = Enumerable.Range(0, times.Count() - 1)
    .Select(i => new TimeSlot(times.ElementAt(i), times.ElementAt(i + 1), ""));

foreach(TimeSlot ts in slicedSlots )
{                
    ts.Name = String.Join(",", 
        originalSlots
        .Where(origTS => ts.StartDate >= origTS.StartDate && ts.EndDate <= origTS.EndDate)
        .Select(origTS => origTS.Name)
    );
    Console.WriteLine(ts);
}

手动完成的分析:

Original slots: 

     |------- 1 -------|
              |- 2 -| 
        |--- 3 --|
     |  |  |  |  |  |  |
    00 05 10 15 20 25 30

Sliced slots:

     |-a|--b--|-c|-d|-e|
     |  |  |  |  |  |  |
    00 05 10 15 20 25 30

Each slot contains:

    a: 1
    b: 1,3
    c: 1,2,3
    d: 1,2
    e: 1

    

CODE 的输出:

5/5/2020 13:00:00 => 5/5/2020 13:05:00 : 1
5/5/2020 13:05:00 => 5/5/2020 13:15:00 : 1,3
5/5/2020 13:15:00 => 5/5/2020 13:20:00 : 1,2,3
5/5/2020 13:20:00 => 5/5/2020 13:25:00 : 1,2
5/5/2020 13:25:00 => 5/5/2020 13:30:00 : 1

我的时间段class:

public class TimeSlot
{

    public DateTime StartDate;
    public DateTime EndDate;
    public String Name;

    public TimeSlot(DateTime start, DateTime end, String name)
    {
        StartDate = start;
        EndDate = end;
        Name = name;
    }

    public override string ToString()
    {
        return $"{StartDate} => {EndDate} : {Name}";
    }

}