我在这个算法中缺少什么来在两个TimeSpans之间找到可能跨越不同日期的TimeOfDay?

时间:2012-12-14 20:18:26

标签: c# .net .net-4.0 timespan

我在24小时内有List<T>个可用时间,以及两个TimeSpans,minTime和maxTime。

我需要找到位于List<T>minTime之间的maxTime内的时间,但是由于在多个时区中使用了该时间,minTime和maxTime可以是在不同的日子和第二天下午1点到凌晨1点之间的差距

我最接近的是这个,但我觉得我在这里缺少一些主要组件,或者做一些非常低效的事情,因为我对TimeSpan对象很新。我只是想不出来......

// Make new TimeSpan out of maxTime to eliminate any extra days (TotalHours >= 24),
// then check if time on the MaxTime is earlier than the MinTime
if (new TimeSpan(maxTime.Hours, maxTime.Minutes, maxTime.Seconds) < minTime)
{
    // If time on MaxTime is earlier than MinTime, the two times span separate days,
    // so find first time after minTime OR before maxTime
    nextAvailableTime = Times.FirstOrDefault(p =>
        (p.Time.TimeOfDay >= minTime || (p.Time.TimeOfDay < maxTime))
        && p.Count < ConcurrentAppointments);
}
else
{
    // If time on MaxTime is later than MinTime, the two times are for the same day
    // so find first time after minTime AND before maxTime
    nextAvailableTime = Times.FirstOrDefault(p =>
        (p.Time.TimeOfDay >= minTime && p.Time.TimeOfDay < maxTime)
        && p.Count < ConcurrentAppointments);
}

Times列表使用EST(我的当地时间),但minTimemaxTime可以基于其他时区。

例如,如果我们为夏威夷时区运行此算法,我们最终将拥有minTime = new TimeSpan(13, 0, 0)maxTime = new TimeSpan(25, 0, 0),从早上8点到晚上8点HST =下午1点 - 美国东部时间凌晨1点。

Times集合是List<AppointmentTime>AppointmentTime是一个如下所示的类:

class AppointmentTime
{
    DateTime Time { get; set; } 
    int Count { get; set; }
}

我很确定我在这里缺少一些重要的东西,或者应该有一种更有效的方式来做这件事,我不知道,但我真的想不出它会是什么。我的算法有问题吗?或者更有效的方法是在两个TimeOfDay之间找到可能跨越不同日期的TimeSpans

更新

我根据CasperOne's answer找出了我遗失的内容。我忘记了日期确实很重要,因为我的时间跨越了不同的时区。

使用我上面的夏威夷时区示例,安排星期一的约会将导致在周日晚上错误地安排夏威夷预约。

我的解决方案是在为24小时工作日的“第一个窗口”安排约会之前检查前一天是否有效,并在与.AddDays(maxTime.Days)比较时调整约会日期maxTime

// If time on MaxTime is earlier than MinTime, the two times span separate days,
// so find first time after minTime OR before maxTime if previous day has appointments set as well
var isPreviousDayValid = IsValidDate(AppointmentDate.AddDays(-1));

nextAvailableTime = Times.FirstOrDefault(p =>
    (p.Time.TimeOfDay >= minTime 
        || (p.Time.AddDays(maxTime.Days).TimeOfDay < maxTime && isPreviousDayValid)
    ) && p.Count < ConcurrentAppointments);

2 个答案:

答案 0 :(得分:1)

一般的想法是,不要比较,比较日期;将您的窗户从时间转换为日期,其余的很容易。

您可以为列表中的每个项生成一组新的DateTime实例,以比较最小值和最大值,使用Date property作为计算基础您想要比较的范围上的上限。

这假定您的minTime始终小于maxTime,如果your window covers more than one day您通过Hours property值表示重叠到新的一天的范围{3}} TimeSpan

您必须查看之前之前的日期以及之后的日期。例如:

   (1)  (2)  (3)  (4)  (5)
----x----x----x----x----x----

(1) - 1/1/1900 11:00 PM - Date component in your list - 1 day + min time
(2) - 1/2/1900 12:05 AM - this is the date and time from your list
(3) - 1/2/1900 01:00 AM - Date component in your list - 1 day + max time
(4) - 1/2/1900 11:00 PM - Date component in your list + min time
(5) - 1/3/1900 01:00 AM - Date component in your list + max time

这意味着您需要创建两个窗口并检查以下内容:

nextAvailableTime = Times.FirstOrDefault(p => {
    // Check count first, get this out of the way.
    if (!(p.Count < ConcurrentAppointments)) return false;

    // The date time and the date component
    DateTime dt = p.Time;
    DateTime d = dt.Date;

    // The windows
    DateTime prevWindowMin = d.AddDays(-1) + minTime;
    DateTime prevWindowMax = d.AddDays(-1) + maxTime;
    DateTime windowMin = d + minTime;
    DateTime windowMax = d + maxTime;

    // Is it in *either* window;
    return
        (prevWindowMin <= dt && dt <= prevWindowMax)||
        (windowMin <= dt && dt <= windowMax);
});

您的问题并不完全清楚,但如果当天的某些时间是其他而不是列表中项目的Date组件,则可以替换p.Time对于Date组件日期(减去适当的一天来创建窗口),它应该可以工作。

答案 1 :(得分:1)

@Rachel,你能提供一个使这个无法使用的反例吗?

nextAvailableTime = Times.OrderBy(i => i.Time).FirstOrDefault(i => i.Count < ConcurrentAppointments &&
    i.Time.TimeOfDay >= minTime &&
    i.Time.TimeOfDay < maxTime
            );

<强> [编辑]

所以,以下应该有效。我理解它的方式,问题是如何有效地找到TimeOfDay的最小AppointmentTime,遵循TimeOfDayminTime之间的最小值maxTime }。对于1,000,000次迭代,Rachel的代码运行时间约为0.55秒

var Times = new List<AppointmentTime>();
var ConcurrentAppointments = 10;
Times.AddRange(new[]{
    new AppointmentTime()
    {
        Count = 0,
        Time = new DateTime(2012, 12, 1, 1, 30, 0)
    },
    new AppointmentTime()
    {
        Count = 0,
        Time = new DateTime(2012, 12, 1, 13, 5, 0)
    },
    new AppointmentTime()
    {
        Count = 0,
        Time = new DateTime(2012, 12, 1, 11, 0, 0)
    }});

var minTime = new TimeSpan(13, 0, 0);
var maxTime = new TimeSpan(25, 0, 0);

// Version 1
// Not so performant, ~0.48 seconds for a loop of 1,000,000 iterations, see Version 2

//nextAvailableTime = Times.OrderBy(i => i.Time).FirstOrDefault(i => i.Count < ConcurrentAppointments &&
//    i.Time.TimeOfDay.TotalSeconds >= Math.Min(maxTime.TotalSeconds % (3600 * 24), minTime.TotalSeconds)
//    );

// Version 2
// Better performance, ~0.12 seconds for 1,000,000 iterations. We calculate the 
// constant value we are comparing with outside the lambda expression

// We calculate the `totalSeconds` variable as the minimum of seconds within the 
// 24h day. For that, we use the `% (3600 * 24)` operation to exclude the days.
var totalSeconds = (int)Math.Min(maxTime.TotalSeconds % (3600 * 24), minTime.TotalSeconds);

// We create a timespan variable called `timeOfDay` which is based on the
// `totalSeconds` variable above. Note that the day is not essential.
var timeOfDay = (new DateTime(1, 1, 1, totalSeconds / 3600, (totalSeconds % 3600) / 60, totalSeconds % 60)).TimeOfDay;

// Returns the `AppointmentTime` with the 01:30 AM. Note, again, that the 
// date of the `AppointmentTime` is not essential
nextAvailableTime = Times.FirstOrDefault(i => i.Count < ConcurrentAppointments &&
                i.Time.TimeOfDay >= timeOfDay
                );