【问题标题】:Overlapping List of Objects对象的重叠列表
【发布时间】:2015-09-27 07:32:46
【问题描述】:

我有以下

var list = new List<Schedule>();

Schedule 对象具有以下属性:

  • 开始时间(日期时间)
  • 结束时间(日期时间)

是否有一行代码可以检查list 中的时间限制是否重叠,同时遍历其所有Schedule 成员?

提前致谢。

我试图在一个名为 EnforceOverlapPolicy() 的方法中使用上述逻辑,如果有任何冲突,从技术上讲,它会抛出一个新的异常。

【问题讨论】:

  • StartTime 和 EndTime 是同一天的 Time 值吗?
  • @Steve 总是。对于 List<Schedule> 中的每个对象都在同一天。 - 实际上我只是在解析时间,而忽略了实际的Date

标签: c# linq comparison


【解决方案1】:

如果您只关心是否存在重叠而不是哪些时间表重叠,那么您应该能够在 O(n log n) 中执行此操作。关键是通过StartTimeEndTime(O(n log n) 部分)对时间表进行排序。然后您可以枚举结果并将每个计划的StartTime 与前一个计划的EndTime 进行比较。如果StartTime 早于EndTime,那么您有重叠的时间表。但是,如果没有第三方帮助,我认为它不能在单个查询中完成。我自己是Ix-Main 的粉丝。以下解决方案使用来自Ix-MainBuffer 函数:

bool hasOverlap =
    list
    .OrderBy(x => x.StartTime)
    .ThenBy(x => x.EndTime)
    .Buffer(2, 1)
    .Where(x => x.Count == 2)
    .Where(x => x[1].StartTime < x[0].EndTime)
    .Any();

如果您不想使用第三方库,那么您可以使用以下方法执行相同的操作:

Schedule first = null;
bool hasOverlap = false;
using(var enumerator = list.OrderBy(x => x.StartTime).ThenBy(x => x.EndTime).GetEnumerator())
{
    enumerator.MoveNext();
    first = enumerator.Current;
    while(enumerator.MoveNext())
    {
        if(enumerator.Current.StartTime < first.EndTime)
        {
            hasOverlap = true;
            break;
        }
    }
}

【讨论】:

  • hasOverlapTwo 是干什么用的?
【解决方案2】:

假设性能不是问题(否则考虑an algorithm for overlaps):

var pairs = from f in list 
            from s in list
            where (f.EndTime < s.StartTime) && (f.StartTime < s.EndTime) && (f != s)
            select new {First = f, Second = s };

基本上,我们会检查所有可能的配对是否有重叠。

或者,没有查询语法:

var overlaps = list.SelectMany(l => list, (f, s) => new {f,s})
    .Where(x => (x.f.EndTime < x.s.StartTime) && 
               (x.f.StartTime < x.s.EndTime) && (x.f != x.s));

【讨论】:

  • 您好,感谢您的快速回答。你能解释一下这是如何工作的吗?我不确定,但这似乎检查给定列表中的两个 Schedule 对象,对吗?
  • @user1027620 from f in list from s in list 给出了来自list 的所有可能的对组合,然后我们通过检查它们的持续时间是否相交来检查它们是否重叠。这是一个 O(n^2) 解决方案,但对于相对较小(
  • 您的答案似乎与您在答案中添加的链接完全相同。为什么不将其标记为重复?
  • @YuvalItzchakov 因为 OP 正在寻找一种简单的单线方式,而这个问题正在寻找一种有效的算法。所以他们对同一个问题提出了不同的问题。
  • @Steve 正确。 - overlaps 在这种情况下似乎总是返回 Enumeration yielded no results 即使有明显的冲突。 Schedule 1 -> EndTime: {9/27/2015 11:59:59 PM} - StartTime: {9/27/2015 12:00:00 AM}Schedule 2 -> EndTime: {9/27/2015 11:59:59 PM} - StartTime: {9/27/2015 2:00:00 AM}
【解决方案3】:

一种不同的方法,我不确定整个事情的性能,但其想法是在一个 BitArray 中展平您的日程定义的范围,该 BitArray 表示特定分钟是否已被先前的日程使用。

从技术上讲,它也不是一个“单行”的答案,尽管从调用代码的角度来看它只是一行

所以假设已经定义了一个类ScheduleList

public class ScheduleList : List<Schedule>
{
    private BitArray ba = new BitArray(1440);

    // Define own Add method to Add a Schedule to the list
    // Or override the predefined one....
    public Schedule Add(int sh, int sm, int eh, int em)
    {
        Schedule s = new Schedule();
        s.StartTime = new DateTime(1, 1, 1, sh, sm, 0);
        s.EndTime = new DateTime(1, 1, 1, eh, em, 0);

        // Of course, having full control on the Add phase, you
        // could easily enforce your policy at this point. 
        // You could not accept a new schedule if the time slot is busy
        // but we could ignore it at this point

        this.Add(s);
        return s;
    }

    public bool IsTimeSlotBusy(Schedule s)
    {
        int sidx = Convert.ToInt32(TimeSpan.FromMinutes((s.StartTime.Hour * 60) + s.StartTime.Minute).TotalMinutes);
        int eidx = Convert.ToInt32(TimeSpan.FromMinutes((s.EndTime.Hour * 60) + s.EndTime.Minute).TotalMinutes);
        for (int x = sidx; x <= eidx; x++)
        {
            if (ba.Get(x)) return true;
            ba.Set(x, true);
        }
        return false;
    }
}

现在你可以写这样的东西了

ScheduleList schedules = new ScheduleList();
schedules.Add(12, 0, 12, 30);
schedules.Add(15, 10, 15, 30);
schedules.Add(12, 0, 23, 59);
schedules.Add(14, 0, 23, 59);

// The fake 'one liner'
bool overlap = schedules.Any(s => schedules.IsTimeSlotBusy(s));
Console.WriteLine(overlap);

在这里等待数学家告诉我为什么这是最糟糕的。 (不讽刺,我真的很想知道从数学的角度来看这是否不好)

【讨论】:

  • 嗨@Steve,感谢您的回答。你能否解释一下为什么需要BitArray?为什么将值拆分为sh, sm, eh, em 而不是直接DateTime?抱歉,我没有提到这些值将从DateTimePicker 中检索,而Time 值仅可见。我喜欢你接近tbh,但我想了解更多。再次感谢! - 还有一件事,我想直接比较 TotalSeconds 而不是 Hours and Minutes 你的解决方案可以吗?
  • 1440 位数组中的单个位表示一天中一分钟的忙碌状态 (60*24)。如果它已被调度使用,则为 true,否则为 false。添加秒需要将数组放大到 86400 位(60*24*60)来表示每秒。这可能需要更多的检查,而且可能不是一个好的解决方案。您需要检查真实数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多