C# LINQ相当复杂的排序

3

我有一个对象列表,需要将其转换为相当复杂的排序顺序。

尽管我在其他编程语言方面并不是新手,但对于linq和c#/.net我完全是个新手,因此我希望能得到一些方向性的提示。

我的列表(简化版)如下:

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)}
]

我需要将其转化为类似于以下的内容:
我需要将其转化为可读性更强的文本。
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)}
]

复杂的排序规则如下:按照 originatesFrom 进行分组,每个组内部按照 lastChanged 降序排序,并且组按照第一个实例的 lastChanged 升序排序。最后,originateFrom 中为 null 的应该放在每个组的底部,与 originateFrom 中的 nr 匹配。 (是的-这不是开箱即用的东西:-/)我已经尝试按 originatesFrom 对它们进行分组,但我真的需要将它们分开,以便可以单独对它们进行排序(我想?),而且我不确定是否明智使用 linq,而不是逐一处理所有对象,构建多个列表,最终将它们连接起来?好的一面是,很少会有真正的许多对象(因此效率可能不是太大的问题)。在排序之前,我不知道会有多少组,每个组会有多少对象。如果需要更好地解释排序规则,请告诉我!

为什么 originatesFrom = 1 在 originatesFrom = 2 和 originatesFrom = 3 之间? - sgmoore
@sgmoore: 由于第一项的 lastChanged - Jon Skeet
1个回答

4
假设这实际上是在 List<T> 或类似的数据结构中(而不是通过 IQueryable<T> ,我们可能需要考虑查询是否可被翻译),我认为这样应该可以工作:
  • 按 "originatesFrom 如果非空;否则按 nr" 分组
  • 将每个分组转换为一个列表,按 originatesFrom (降序)和 lastChanged (降序)排序 - 这将使空的 originatesFrom 保持在最后
  • 按 "first itemlastChanged"(升序)排序此列表
  • 使用 SelectMany 压平结果
以下是针对你的示例数据的完整示例:
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);
        }        
    }
}

输出结果与您所需的顺序相匹配:

{ 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 }

不是LINQ太棒了吗?

哦天啊 - 看起来好简单啊!! <3 ... 我希望这个能够工作,但首先必须试一下(不幸的是我现在无法尝试)。但如果这行得通,我真的需要学习如何使用linq! :-D 非常感谢@daisy-shipton - hasse
1
@hasse:像许多最强大的工具一样,它很容易使用,但需要一段时间来学习。当然,这只是一种方法,但这是我能想到的最简单的开始。 - Jon Skeet
1
@ThierryV:如果项目的分组键为x.OriginatesFrom且非空,则为其设置该键,否则使用x.Number?? 运算符是 null 合并运算符 - 如果左操作数非空,则返回左操作数,否则返回右操作数。 - Jon Skeet
@DaisyShipton 我理解什么是 ?? 运算符。但我想知道 C# 是如何在 OriginatesFrom 和 Number 之间选择 GroupBy 的值的。 - Antoine V
1
@daisy-shipton:目前我还无法在真实数据上进行测试,但它确实在我的测试数据集上起到了作用!:-) 我完全被它只需如此少的步骤和相对人类可读性所迷住了!我几乎认为你的代码比我试图解释排序规则的微弱尝试更易读!:-D - hasse
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接