score:1

Accepted answer

Here is a suggestion where TimeAndCode.Code is an enum rather than a string.

It will always order entries with indentical timespans in a repeated CO, GO, BT pattern; meaning that if e.g. five entries with identical timespans has the following Code selection: 2 x CO, 1 x BT, 2 x GO, it will always order them as CO, GO, BT, CO, GO (as opposed to CO, GO, CO, GO, BT).

I achieve this by generating an OrderBy property based on the timespan, an index (generated inside a nested group) and the numerical Code value for each entry.

Using the following types:

public struct TimeAndCode
{
    public TimeSpan Time { get; set; }
    public Code Code { get; set; }
}

public enum Code
{
    Undefined,
    CO,
    GO,
    BT
}

we can write the following expression:

List<TimeAndCode> result = list
    .GroupBy(entry => entry.Code)
    .SelectMany(gr => gr
        .GroupBy(entry => entry.Time)
        .SelectMany(gr => gr.Select((entry, index) => (
            OrderBy: entry.Time.ToString() + index + (int)entry.Code,
            TimeAndCode: entry))))
    .OrderBy(entry => entry.OrderBy)
    .Select(entry => entry.TimeAndCode)
    .ToList();

where list is a List<TimeAndCode>.


Using example input as follows:

List<TimeAndCode> list = new List<TimeAndCode> 
{
    new TimeAndCode { Time = new TimeSpan(09, 00, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.BT },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(08, 00, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.BT },
    new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.GO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO },
    new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO },
};

, after applying the Linq expression, we can then print the result

foreach (var entry in result)
{
    Console.WriteLine("Time: " + entry.Time + " Code: " + entry.Code);
}

and get the following output:

Time: 08:00:00 Code: CO
Time: 09:00:00 Code: GO
Time: 09:30:00 Code: CO
Time: 09:30:00 Code: GO
Time: 09:30:00 Code: BT
Time: 09:30:00 Code: CO
Time: 09:30:00 Code: GO
Time: 12:30:00 Code: CO
Time: 12:30:00 Code: GO
Time: 13:30:00 Code: CO
Time: 13:30:00 Code: GO
Time: 13:30:00 Code: BT
Time: 13:30:00 Code: CO
Time: 13:30:00 Code: GO

score:1

It's a variation of Astrid's:

var r =  list.GroupBy(tc => tc)
        .SelectMany(g => g.Select((tc, i) => (tc, i)))
        .OrderBy(t => (t.tc.Time, t.i, t.tc.Code))
        .Select(t => t.tc);

With the following precursor setup:

public record TimeAndCode(TimeSpan Time, Code Code);

public enum Code { CO, GO, BT }

    ...

    var list = new List<TimeAndCode> 
        {
            new (TimeSpan.FromHours(8), Code.CO),
            new (TimeSpan.FromHours(9), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.CO),
            new (TimeSpan.FromHours(9.5), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.GO),
            new (TimeSpan.FromHours(9.5), Code.CO),
            new (TimeSpan.FromHours(9.5), Code.BT),
            new (TimeSpan.FromHours(12.5), Code.CO),
            new (TimeSpan.FromHours(12.5), Code.GO),
            new (TimeSpan.FromHours(13.5), Code.CO),
            new (TimeSpan.FromHours(13.5), Code.BT),
            new (TimeSpan.FromHours(13.5), Code.GO),
            new (TimeSpan.FromHours(13.5), Code.CO),
            new (TimeSpan.FromHours(13.5), Code.GO),
        };


TimeAndCode is a record, which means it gains some useful properties for sorting and comparing. It can be simply grouped because it is automatically equal to another TimeAndCode with the same data

Grouping by the time and code results in a list-of-lists; the two 9:30 COs go in a list. Passing this to Select((tc,i) means that i is 0 for the first and 1 for the second etc.. All we do with this is promote it to a tuple of the tc and the i because we'll need them later, and we SelectMany to undo the GroupBy that allowed us to count the same Time/Code

We OrderBy another tuple; tuples sort by their values in order, so to order by 3 things a, b, and c, we can OrderBy a tuple of (a, b, c)

And all that is left to do at the end is select the tc back to make a list of TimeAndCode, sorted something like what you want

Like Astrid's it also doesn't put the BT at the end for 9:30 - it goes in the middle, like 13:30's does.. But you haven't provided an explanation for why 9:30's BT is at the end...


Related Articles