连续日期分组

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 等方法来实现。

谢谢。


你能否按周进行分组,然后计算组内的项目数量?按周分组可以在这里找到:https://dev59.com/HWoy5IYBdhLWcg3wa9ff - Luke Baughan
不行,因为其中可能有2.5周的星期一至星期五。 - jmasterx
啊哈,我明白了,你想看到12.5而不是5,5,2.5。 - Luke Baughan
这有帮助吗?https://dev59.com/0k7Sa4cB1Zd3GeqP2lNw - Luke Baughan
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是序列的计数。虽然通常使用FirstCount都会对源序列进行两次迭代,但我们知道由GroupWhile返回的序列实际上是底层的List,因此多次迭代它不是问题,并且获取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(),
                });
}

@plutonix 有些可能不是立即明显的,但在这种情况下,实际上只是复制/粘贴... - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接