无穷迭代器的交集集算法

时间:2016-02-28 23:54:50

标签: c# algorithm iterator infinite-loop

我必须实现类似于Outlook Meeting Organizer的调度算法,其中我有几个人参加会议,组织者找到所有来自邀请者的人都可用的时间段。所以,让我们说我有第三方服务实现以下界面:

B third = new B(12);

DateTimeInterval是:

interface IAvailabilityProvider
{
     IEnumerable<DateTimeInterval> GetPersonAvailableTimeSlots(
              string personName, DateTime startFrom);
}

GetPersonAvailableTimeSlots返回无限迭代器,它将枚举人工作时间的所有时间段,不包括周末和假日等等,对未来无限。

我的任务是实现一个函数,该函数接受一组迭代器并返回交叉点的另一个迭代器:

class DateTimeInterval{
   public DateTime Start {get;set;}
   public TimeSpan Length {get;set;}
}

当所有人都可用时,它获取所有人的可用时隙的迭代器并返回相交的时隙。在内部我必须实现以下功能:

IEnumerable<DateTimeInterval> GetIntersections(
    string[] persons, DateTime startFrom);

1 个答案:

答案 0 :(得分:2)

对我来说,解决方案似乎非常简单。

    static IEnumerable<DateTimeInterval> GetIntersections(IEnumerable<DateTimeInterval>[] personsAvailableSlots)
    {
        var enumerators = personsAvailableSlots.Select(timeline => timeline.GetEnumerator()).ToArray();

        // Intersection is empty when at least one of iterators is empty.
        for (int i = 0; i < personsAvailableSlots.Length; i++) if (!enumerators[i].MoveNext()) yield break;

        while (true)
        {
            // first we ensure that intersection exists at the current state
            // if not so, we have to move some iterators forward
            var start = enumerators.Select(tl => tl.Current).Max(interval => interval.Start);

            foreach (var iter in enumerators)
                while (iter.Current.Start + iter.Current.Length <= start)
                    if (!iter.MoveNext()) yield break;

            // now we check if the interval exists
            var int_start = enumerators.Select(tl => tl.Current).Max(interval => interval.Start);
            var int_end = enumerators.Select(tl => tl.Current).Min(interval => interval.Start + interval.Length);

            if (int_end > int_start)
            {
                //if so, we return it
                yield return new DateTimeInterval()
                {
                    Start = int_start,
                    Length = int_end - int_start
                };

                // and, finally, we ensure next interval to start after the current one ends
                //
                // CAUTION: We are able to move iterators whose current interval have passed only. 
                // We will miss huge spans which cover several intervals in other iterators otherwise.
                //
                // In fact we should move the only inerator - that one wich currently limits the last result
                foreach (var iter in enumerators)
                    while (iter.Current.Start + iter.Current.Length == int_end)
                        if (!iter.MoveNext()) yield break;
            } 
        }
    }

我已经针对几个简单的场景对此进行了测试,希望我不会遗漏一些重要的事情。