Search code examples
c#collectionsgroupingdayofweeknodatime

Grouping a list of days of week into groups of consecutive days


I am using C# to create a function that takes in a list of NodaTime.IsoDayOfWeek days. I want to group the input into groups of consecutive days.

For example, the following lists should give the following output:

{ Mon, Tue } => { { Mon, Tue } }
{ Mon, Wed } => { { Mon }, { Wed } }
{ Mon, Tue, Fri, Sat } => { { Mon, Tue }, { Fri, Sat } }
{ Mon, Wed, Fri, Sun } => { { Sun, Mon }, { Wed }, { Fri } }
{ Mon, Tue, Wed, Thu, Fri, Sat, Sun } => { { Mon, Tue, Wed, Thu, Fri, Sat, Sun } }

Notice that Sunday and Monday are consecutive, so the list is a closed loop. In addition, the resulting lists should be ordered such that the first day directly follows a day that is not included in the input list (or Monday if the complete list is included).

Mauricio Scheffer published a great extension method to group consecutive integers here:

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> list) {
    var group = new List<int>();
    foreach (var i in list) {
        if (group.Count == 0 || i - group[group.Count - 1] <= 1)
            group.Add(i);
        else {
            yield return group;
            group = new List<int> {i};
        }
    }
    yield return group;
}

However I can't figure out how to modify this to group days since Sunday and Monday are also consecutive. How can I group consecutive days where Sunday and Monday are also considered consecutive?


Solution

  • This is the solution I ended up going with. I've used Linq here, but it could be easily rewritten without. I also wrote an extensive set of unit tests for this, please comment if you would like access to them.

    using NodaTime;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Domain.Extensions
    {
        public static class IsoDayOfWeekExtensions
        {
            public static IReadOnlyList<IReadOnlyList<IsoDayOfWeek>> GroupConsecutive(this IList<IsoDayOfWeek> days)
            {
                var groups = new List<List<IsoDayOfWeek>>();
                var group = new List<IsoDayOfWeek>();
    
                var daysList = days.Distinct().OrderBy(x => (int)x);
                foreach (var day in daysList)
                {
                    if (!group.Any() || (int)day - (int)group.Last() == 1)
                    {
                        group.Add(day);
                    }
                    else
                    {
                        groups.Add(group);
                        group = new List<IsoDayOfWeek>() { day };
                    }
                }
    
                // Last group will not have been added yet. Check if the last group can be combined with the first group (Sunday and Monday are also consecutive!)
                if (group.Contains(IsoDayOfWeek.Sunday) && groups.Any() && groups.First().Contains(IsoDayOfWeek.Monday))
                {
                    // Insert before the Monday so that the days are in the correct consecutive order.
                    groups.First().InsertRange(0, group);
                }
                else
                {
                    groups.Add(group);
                }
    
                return groups.Select(x => x.ToList()).ToList();
            }
        }
    }