Подтвердить что ты не робот

Группировка смежных дат

У меня есть 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 или что-то еще.

Спасибо

4b9b3361

Ответ 1

Итак, мы начнем с этой общей функции итератора. Он принимает последовательность и предикат, который принимает два элемента и возвращает логическое значение. Он будет считываться в элементах из источника, и в то время как элемент вместе с предыдущим элементом возвращает 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(),
                });
}