13

我有一个List<DateTime> dates;

我有一堂课:

class NonWorkingDay
{
   public DateTime Start;
   public int Days;
}

我正在尝试找出一种将它们分组的干净方法。

public List<NonWorkingDay> GetContiguousDates(List<DateTime> dates)
{

}

注意:如果周五有 NWD,而下一个是周一,则应将它们分组。周末不考虑。

例如,如果我有

September 3 2013
September 20 2013
September 23 2013
September 24 2013
September 30 2013
October 1  2013

输出将是:

Start = September 3 2013, Days = 1
Start = September 20 2013, Days = 3 //weekend got skipped
Start = September 30 2013, Days = 2

有没有办法做到这一点(没有一堆计数器变量)并使用 .Select 或 .Where 或其他东西。

谢谢

4

1 回答 1

20

因此,我们将从这个通用迭代器函数开始。它接受一个序列和一个接受两个项目并返回一个布尔值的谓词。它将从源中读取项目,而一个项目连同它的前一个项目,根据谓词返回 true,下一个项目将在“下一个组”中。如果返回 false,则前一组已满,并开始下一组。

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> source
    , Func<T, T, bool> predicate)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        List<T> currentGroup = new List<T>() { iterator.Current };
        while (iterator.MoveNext())
        {
            if (predicate(currentGroup.Last(), iterator.Current))
                currentGroup.Add(iterator.Current);
            else
            {
                yield return currentGroup;
                currentGroup = new List<T>() { iterator.Current };
            }
        }
        yield return currentGroup;
    }
}

我们还需要这个简单的帮助方法,它可以根据日期获取下一个工作日。如果您还想合并假期,它会从微不足道变得非常困难,但这就是逻辑所在。

public static DateTime GetNextWorkDay(DateTime date)
{
    DateTime next = date.AddDays(1);
    if (next.DayOfWeek == DayOfWeek.Saturday)
        return next.AddDays(2);
    else if (next.DayOfWeek == DayOfWeek.Sunday)
        return next.AddDays(1);
    else
        return next;
}

现在把它们放在一起。首先,我们订购日子。(如果您确保它们始终按顺序排列,则可以删除该部分。)然后我们将连续的项目分组,而每个项目是前一个工作日的下一个工作日。

那么我们需要做的就是把一个IEnumerable<DateTime>连续的日期变成一个NonWorkingDay。因为开始日期是第一个日期,并且Days是序列的计数。虽然通常使用两者First并且Count会迭代源序列两次,但我们碰巧知道返回的序列GroupWhile实际上是List引擎盖下的 a,因此多次迭代它不是问题,并且得到Count偶数是 O(1)。

public IEnumerable<NonWorkingDay> GetContiguousDates(IEnumerable<DateTime> dates)
{
    return dates.OrderBy(d => d)
            .GroupWhile((previous, next) => GetNextWorkDay(previous).Date == next.Date)
            .Select(group => new NonWorkingDay
                {
                    Start = group.First(),
                    Days = group.Count(),
                });
}
于 2013-03-14T16:16:28.883 回答