したがって、この一般的なイテレータ関数から始めます。シーケンスと、2 つの項目を受け入れてブール値を返す述語を使用します。ソースからアイテムを読み取り、アイテムが前のアイテムとともに述語に基づいて 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
を使用してソース シーケンスを 2 回反復しますが、 によって返されるシーケンスが実際には内部Count
であることがわかっているため、複数回反復しても問題はなく、 を取得しても O(1) です。GroupWhile
List
Count
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(),
});
}