所以,我们将从这个通用的迭代器函数开始。它接受一个序列和一个谓词,该谓词接受两个项目并返回一个布尔值。它将从源中读取项目,并在一个项目以及它的前一个项目基于谓词返回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
,因此多次迭代它不是问题,并且获取
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(),
});
}