高效的可用时间查找器

时间:2011-04-17 19:20:47

标签: c# algorithm

我在特定的工作日有一组约会,从早上8点到下午6点:

任命1:上午9点至上午11点 预约2:下午2点至下午5点

我正在寻找一种有效的方式来找到空闲时间。所以在这种情况下,可用时间是:

8 am-9am 11 am-2pm 5时至6时

所以我有一个TimeBlock类

class TimeBlock {
  public DateTime start
  public DateTime end
}

var appointments = new List<TimeBlock>();
var freeTimeBlocks = new List<TimeBlock>();

' add appointments
appointments.Add(new TimeBlock{start...
appointments.Add(new TimeBlock{start...

我正在寻找一种有效的方法来寻找空闲时间,因为算法将在一个非常大的数据集上运行。

5 个答案:

答案 0 :(得分:2)

确保时间块已排序(O(nlogn)或更好),然后循环遍历它们并创建从每个块的末尾到下一个块的开头(O(n))的可用性范围。

答案 1 :(得分:2)

我认为这种方法对于大型数据集(d * u&gt;&gt; n)来说是渐近非常有效的,只要它们可以适合内存:

如果天数是d,用户数是u,每个用户每天的平均约会数是n,这是O(d * u * n),而基于排序的个人日方法会更像是O(d * u * n * log n)。

(如果u = 1或d = 1,则无关紧要 - 没有区别。)

  1. 创建一个由所有列表索引的列表数组 可能的时间段。例如,如果 你可以每5个时间段 分钟,你会有一个数组 大小120.这一步是O(1)。我们假设可能的时隙数是固定的,因此它应该与复杂性分析无关。

  2. 循环所有约会,所有天和所有用户,并添加约会(连同记录对于这两个约会开始和约会结束时间的相应列表。如果您使用链接列表并且每次都将其添加到列表的开头,则此步骤为O(d * u * n)。

  3. 创建一个数组来记录每一天和用户的“当前”空闲时段 - 您将在下一步中看到它的工作原理。

  4. 循环遍历第一个数组,并为数组中的每个列表循环遍历该列表(因此,循环是嵌套的)。对于每个约会,您看到这是“现在结束约会”,开始记录那个时间,当天和用户的新空闲时间段。对于每个约会,你看到这是一个“现在开始预约”,完成记录当天和用户的空闲时间段(如果有的话),如果它是零持续时间则丢弃它 - 如果没有,并且它不是8am,则创建一。这一步也是O(d * u * n)。

  5. 在下午6点完成所有空闲时间记录。这一步是最坏的情况O(d * u)。

  6. 无需进行预约间比较或搜索!

    这是基于基数排序。

    但是,我不确定这在实践中是否会更好 。它当然需要更多的空间!

答案 2 :(得分:1)

试试这个,它假定没有重叠的约会:

var orderedAppointments = appointments.OrderBy(a => a.start).ToArray();

freeTimeBlocks.Clear();

for(int i = 0; i < orderedAppointments.Length - 1; i++)
{
    freeTimeBlocks.Add(new TimeBlock(){ start = orderedAppointments[i].end; end = orderedAppointments[i + 1].start });
}
var firstAppointment = orderedAppointments.First();
var lastAppointment = orderedAppointments.Last();

if(firstAppointment.start.Hour > 8)
   freeTimeBlocks.Add(new TimeBlock() { start = firstAppointment.Today.AddHours(8); end = firstAppointment.start });
if(lastAppointment.end.Hour < 18)
   freeTimeBlocks.Add(new TimeBlock() { start = lastAppointment.end; end = lastAppointment.Today.AddHours(18) });

答案 3 :(得分:1)

如果您没有任何重叠约会(从您的问题陈述中不可能),可能就像这样容易:

appointments.Sort((a,b) => a.Start.CompareTo(b.Start));
for(int i = 0; i< appointments.Count-1; i++)
{
    if(appointments[i].End < appointments[i+1].Start)
        freeTimeBlocks.Add(new TimeBlock() { Start  = appointments[i].End, End = appointments[i+1].Start });
}

这不考虑第一次约会之前和最后约会之后的时间边缘情况,那些你必须手动检查和添加的情况。

答案 4 :(得分:1)

此答案还可以假设没有重叠的约会。此解决方案允许使用一个或多个可用时间段,而不是使用问题中给出的上午8点至下午6点的时间范围。

我认为这可以做得更优雅,但我不知道该怎么做。

private static List<TimeBlock> GetDayOpenSlots(IEnumerable<TimeBlock> daySlots,
    IReadOnlyCollection<TimeBlock> usedDaySlots)
{
    var freeTimeBlocks = new List<TimeBlock>();
    foreach (var daySlot in daySlots)
    {
        var selectedSlotsWithinRange = usedDaySlots
            .Where(x => x.StartTime >= daySlot.StartTime && x.EndTime <= daySlot.EndTime)
            .OrderBy(x => x.StartTime)
            .ToList();

        if (!selectedSlotsWithinRange.Any())
        {
            freeTimeBlocks.Add(daySlot);
            continue;
        }

        for (var i = 0; i < selectedSlotsWithinRange.Count - 1; i++)
        {
            var currentSlot = selectedSlotsWithinRange[i];
            var nextSlot = selectedSlotsWithinRange[i + 1];
            if (currentSlot.EndTime == nextSlot.StartTime)
            {
                continue;
            }

            freeTimeBlocks.Add(new TimeBlock(currentSlot.EndTime,
                nextSlot.StartTime));
        }

        var firstAppointment = selectedSlotsWithinRange.First();
        var lastAppointment = selectedSlotsWithinRange.Last();

        if (firstAppointment.StartTime > daySlot.StartTime)
        {
            freeTimeBlocks.Add(new TimeBlock(daySlot.StartTime,
                firstAppointment.StartTime));
        }

        if (lastAppointment.EndTime < daySlot.EndTime)
        {
            freeTimeBlocks.Add(new TimeBlock(lastAppointment.EndTime, daySlot.EndTime));
        }
    }

    return freeTimeBlocks
        .OrderBy(x => x.StartTime)
        .ToList();
}