2

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?

08Dc91wk
  • 4,254
  • 8
  • 34
  • 67
  • 1
    [Check this](http://stackoverflow.com/questions/4681949/use-linq-to-group-a-sequence-of-numbers-with-no-gaps) - Loved dtb's answer - see how you can adapt it to your need – Gilad Green Jul 18 '16 at 09:52

3 Answers3

1

The sample input is sequenced array. Imagine your input is array from 1 to 7 and not in sequence, you have to use 2 loops to find each next number by condition Abs(current-next) == 1 || Abs(current-next) == 6. This is my idea for your solution:

public static IEnumerable<IEnumerable<int>> GroupDay(IEnumerable<int> list)
    {
        List<int> input = new List<int>(list);

        while (input.Count > 0)
        {
            int i = input[0];
            var group = new List<int>();
            group.Add(i);
            input.RemoveAt(0);

            for (int j = 0; j < input.Count; )
            {
                if (Math.Abs(group[group.Count - 1] - input[j]) == 1
                    || Math.Abs(group[0] - input[j]) == 6)
                {
                    group.Add(input[j]);
                    input.RemoveAt(j);
                }
                else
                {
                    j++;
                }
            }

            // Sort output
            group.Sort((x, y) => {
                if (Math.Abs(x - y) == 6)
                {
                    // Sunday and Monday case
                    return y - x;
                }
                else
                    return x - y;
            });
            yield return group;
        }
    }
Nam Tran
  • 643
  • 4
  • 14
1

I did not use LINQ more effectively but I think this will do. this is just a simple console App.

 public enum Days
        {
            Mon = 1,
            Tue,
            Wed,
            Thur,
            Fri,
            Sat,
            Sun
        }






public static IEnumerable<IEnumerable<int>> GroupDay(IEnumerable<int> ListOfDays)        
{
            List<List<int>> Response = new List<List<int>>();
            List<int> Queue = new List<int>();
            var ListToIterate = ListOfDays.Distinct().OrderBy(d => d).ToList();
            foreach (var item in ListToIterate)
            {

                if (Queue.Count == 0)
                {
                    Queue.Add(item);
                }
                else
                {
                    if ((item - 1) == Queue[Queue.Count - 1])
                    {
                        Queue.Add(item);
                    }
                    else if (item != (int)Days.Sun)
                    {
                        Response.Add(Queue);
                        Queue = new List<int>() { item };
                    }
                }

                if (item == ListToIterate.LastOrDefault())
                    Response.Add(Queue);

                //Handle Sunday
                if (item == (int)Days.Sun)
                {
                    //Check if Saturday exists, if exists then do not put sunday before Monday.
                    var FindSaturday = Response.Where(r => r.Contains((int)Days.Sat)).FirstOrDefault();
                    if (FindSaturday == null)
                    {
                        var FindMonday = Response.Where(r => r.Contains((int)Days.Mon)).FirstOrDefault();
                        if (FindMonday != null)
                        {
                            FindMonday.Insert(0, item);
                        }
                    }

                }

            }
            return Response;
        }

and here is how I tried some use cases.

//List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon), DaysToNumber(Days.Tue) };
            //List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon), DaysToNumber(Days.Wed) };
            //List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon), DaysToNumber(Days.Tue), DaysToNumber(Days.Fri), DaysToNumber(Days.Sat) };
            //List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon), DaysToNumber(Days.Wed), DaysToNumber(Days.Fri), DaysToNumber(Days.Sun) };
            //List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon), DaysToNumber(Days.Tue), DaysToNumber(Days.Wed), DaysToNumber(Days.Thur), DaysToNumber(Days.Fri), DaysToNumber(Days.Sat), DaysToNumber(Days.Sun) };
            List<int> ListOfDays = new List<int>() { DaysToNumber(Days.Mon),DaysToNumber(Days.Fri),  DaysToNumber(Days.Sun) };
            var ListToIterate = ListOfDays.Distinct().OrderBy(d => d).ToList();
            var result = GroupDay(ListToIterate);
Nirav Shah
  • 67
  • 1
  • 2
0

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();
        }
    }
}
08Dc91wk
  • 4,254
  • 8
  • 34
  • 67