Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# linq rather complex sorting

Tags:

c#

sorting

linq

I have a list of objects I need to massage into a rather complex sort order.

I am a complete newbie to linq and c#/.net (but not to programming in other languages), so I would love some hints in what direction I need to go.

My list (simplified) looks like this:

List[
    {nr:  1, originatesFrom: null, lastChanged: DateTime(2018,5,3)},
    {nr:  2, originatesFrom: null, lastChanged: DateTime(2018,5,3)},
    {nr:  3, originatesFrom: null, lastChanged: DateTime(2018,5,3)},
    {nr:  4, originatesFrom: 1,    lastChanged: DateTime(2018,5,1)},
    {nr:  5, originatesFrom: 2,    lastChanged: DateTime(2018,5,1)},
    {nr:  6, originatesFrom: 1,    lastChanged: DateTime(2018,5,7)},
    {nr:  7, originatesFrom: 1,    lastChanged: DateTime(2018,5,4)},
    {nr:  8, originatesFrom: 3,    lastChanged: DateTime(2018,5,13)},
    {nr:  9, originatesFrom: 1,    lastChanged: DateTime(2018,5,13)},
    {nr: 10, originatesFrom: 3,    lastChanged: DateTime(2018,5,10)},
    {nr: 11, originatesFrom: 3,    lastChanged: DateTime(2018,5,18)}
]

and I need to massage it into something like this:

List[
    {nr:  5, originatesFrom: 2,    lastChanged: DateTime(2018,5,1)},
    {nr:  2, originatesFrom: null, lastChanged: DateTime(2018,5,3)},
    {nr:  9, originatesFrom: 1,    lastChanged: DateTime(2018,5,13)},
    {nr:  6, originatesFrom: 1     lastChanged: DateTime(2018,5,7)},
    {nr:  7, originatesFrom: 1,    lastChanged: DateTime(2018,5,4)},
    {nr:  4, originatesFrom: 1,    lastChanged: DateTime(2018,5,1)},
    {nr:  1, originatesFrom: null, lastChanged: DateTime(2018,5,3)},
    {nr: 11, originatesFrom: 3,    lastChanged: DateTime(2018,5,18)},
    {nr:  8, originatesFrom: 3,    lastChanged: DateTime(2018,5,13)},
    {nr: 10, originatesFrom: 3,    lastChanged: DateTime(2018,5,10)},
    {nr:  3, originatesFrom: null, lastChanged: DateTime(2018,5,3)}
]

The complex sorting rules are as follows: Grouped by originatesFrom, within each group sorted by lastChanged descenting, and the groups sorted by (first instance's) lastChanged ascending. Finally the ones with null in originatesFrom should be stuffed in the bottom of each group matching their nr in originatesFrom. (yeah - it won't be something that comes out of the box here :-/ )

I have tried to start off by grouping them by originatesFrom, but I really need them to be split appart so I can sort them individually (I think?), and I am not sure it is wise to use linq at all, instead of going through all objects one at a time, building up several lists and ending up concatenating them in the end?

The good part is that there will seldomly be really many objects (so efficiency might not be that big of an issue). I won't know ahead of sorting how many groups there will be, and how many objects there will be in each group.

If I need to explain the sorting rules better, please let me know!

like image 532
hasse Avatar asked Mar 17 '26 15:03

hasse


1 Answers

Assuming this is actually in a List<T> or similar (rather than via an IQueryable<T> where we might need to worry about whether the query can be translated), I believe this should work:

  • Group by "originatesFrom if non-null; nr otherwise"
  • Convert each group into a list, ordered on originatesFrom (descending) then by lastChanged (descending) - this will keep null originatesFrom at the end
  • Order the list by "lastChanged of first item" (ascending)
  • Flatten with SelectMany

Here's a complete example with your sample input data:

using System;
using System.Collections.Generic;
using System.Linq;

class Test
{
    static void Main()
    {
        var items = new[]
        {
             new { Number = 1, OriginatesFrom = (int?) null, LastChanged = new DateTime(2018,5,3) },
             new { Number = 2, OriginatesFrom = (int?) null, LastChanged = new DateTime(2018,5,3) },
             new { Number = 3, OriginatesFrom = (int?) null, LastChanged = new DateTime(2018,5,3) },
             new { Number = 4, OriginatesFrom = (int?) 1, LastChanged = new DateTime(2018,5,1) },
             new { Number = 5, OriginatesFrom = (int?) 2, LastChanged = new DateTime(2018,5,1) },
             new { Number = 6, OriginatesFrom = (int?) 1, LastChanged = new DateTime(2018,5,7) },
             new { Number = 7, OriginatesFrom = (int?) 1, LastChanged = new DateTime(2018,5,4) },
             new { Number = 8, OriginatesFrom = (int?) 3, LastChanged = new DateTime(2018,5,13) },
             new { Number = 9, OriginatesFrom = (int?) 1, LastChanged = new DateTime(2018,5,13) },
             new { Number = 10, OriginatesFrom = (int?) 3, LastChanged = new DateTime(2018,5,10) },
             new { Number = 11, OriginatesFrom = (int?) 3, LastChanged = new DateTime(2018,5,18 )}
        };

        var query = items
            .GroupBy(x => x.OriginatesFrom ?? x.Number)
            .Select(g => g.OrderByDescending(x => x.OriginatesFrom)
                          .ThenByDescending(x => x.LastChanged)
                          .ToList())
            .OrderBy(g => g.First().LastChanged)
            .SelectMany(g => g)
            .ToList();

        foreach (var item in query)
        {
            Console.WriteLine(item);
        }        
    }
}

Output, which matches your required order:

{ Number = 5, OriginatesFrom = 2, LastChanged = 01/05/2018 00:00:00 }
{ Number = 2, OriginatesFrom = , LastChanged = 03/05/2018 00:00:00 }
{ Number = 9, OriginatesFrom = 1, LastChanged = 13/05/2018 00:00:00 }
{ Number = 6, OriginatesFrom = 1, LastChanged = 07/05/2018 00:00:00 }
{ Number = 7, OriginatesFrom = 1, LastChanged = 04/05/2018 00:00:00 }
{ Number = 4, OriginatesFrom = 1, LastChanged = 01/05/2018 00:00:00 }
{ Number = 1, OriginatesFrom = , LastChanged = 03/05/2018 00:00:00 }
{ Number = 11, OriginatesFrom = 3, LastChanged = 18/05/2018 00:00:00 }
{ Number = 8, OriginatesFrom = 3, LastChanged = 13/05/2018 00:00:00 }
{ Number = 10, OriginatesFrom = 3, LastChanged = 10/05/2018 00:00:00 }
{ Number = 3, OriginatesFrom = , LastChanged = 03/05/2018 00:00:00 }

Isn't LINQ wonderful?

like image 61
Jon Skeet Avatar answered Mar 20 '26 12:03

Jon Skeet



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!