【问题标题】:Sorting LINQ 'GroupBy' operator keys into a custom order将 LINQ 'GroupBy' 运算符键排序为自定义顺序
【发布时间】:2020-04-22 07:36:13
【问题描述】:

在 c# 中,当使用 LINQ 运算符“GroupBy”时,我想将键排序为指定的自定义顺序。

我的场景是基于足球的,我想将球员分类到他们预定义的位置(在 CMS 中创建和设置)。玩家可以分配的位置如下,只能归为一个

  • 守门员
  • 后卫
  • 中场
  • 前锋

Player.cs(略读)

    public string Name {get; set;}
    public string ImgUrl {get; set;}
    public string Position {get; set;}

PlayersLandingPageViewModel.cs(略读)

    public IEnumerable<IGrouping<string, TeamPlayerPage>> GroupedPlayersList { get; set; }

PlayersLandingPageController(略读)

    var groupedPlayersList = teamPlayersLandingPage.Children<TeamPlayerPage>().GroupBy(x => x.Position);

    var playersLandingPageViewModel = new PlayersLandingPageViewModel(model.Content)
    {
        GroupedPlayersList = groupedPlayersList
    };

上面返回一个IEnumerable&lt;IGrouping&lt;string, TeamPlayerPage&gt;&gt;,但是键的顺序如下(假设它的默认字母顺序?)。在视图中,我只是遍历 Model.GroupedPlayersList 中的每个组(键),然后是组内的所有玩家。 foreach 中的一个 foreach(按预期工作)

  • 后卫
  • 守门员
  • 中场
  • 前锋

在寻找解决方案时,以下帖子 (how can i create a programmatically sort order in C# on a grouped collection) 建议使用IComparer&lt;String&gt;,但由于缺乏理解,我有点不确定如何将其应用于我的场景。是在 GroupBy 操作之前、之后还是之后?

List<string> playerGroupsSortOrder = new List<string>() { "goalkeeper", "defender", "midfielder", "striker"};

有人可以推荐什么吗?帮助我理解任何有效解决方案的解释或链接将非常有帮助。

其他参考

谢谢

【问题讨论】:

    标签: c# asp.net-mvc list linq umbraco


    【解决方案1】:

    如果我的理解正确,您可以尝试 mapping:排序时,您可以计算每个 Key(“前锋”)所需的顺序(例如,0、1、2、3) , "defender" 等) 即将Key 映射到所需的顺序。

      List<string> playerGroupsSortOrder = new List<string>() { 
        "goalkeeper", "defender", "midfielder", "striker"
      };
    
      ...
    
      var groupedPlayersList = teamPlayersLandingPage
        .Children<TeamPlayerPage>()
        .GroupBy(x => x.Position)
        .OrderBy(group => playerGroupsSortOrder.IndexOf(group.Key));
    

    【讨论】:

    • 您好,感谢您的回复,这在我看来是有道理的,但我试过了,它似乎并没有改变顺序。我仍然让球队被安排为后卫、守门员、中场、前锋。
    • @Paul:请检查名称(和 cases "devender" != "Defender" != "DEFENDER"):如果找不到密钥 IndexOf 将返回不需要的 -1
    • 嗨@Dmitry,是的,你是对的!这是一个外壳问题。 umbraco 中定义的预值是大写的,例如守门员,后卫......一旦我在代码中匹配它现在是正确的顺序。谢谢
    【解决方案2】:

    由于位置的可能值数量有限,enum 似乎是比字符串更好的选择:

    enum Position
    {
        Goalkeeper,
        Defender,
        Midfielder,
        Striker
    }
    

    然后因为枚举只是命名为整数,所以你有你的顺序。守门员 = 0,后卫 = 1 等等。

    更新你的课程:

    public Position Position { get; set; }
    

    然后是 LINQ:

    teamPlayersLandingPage
        .Children<TeamPlayerPage>()
        .GroupBy(x => x.Position)
        .OrderBy(grp => (int)grp.Key);
    

    【讨论】:

    • 嗨,这是有道理的。但是,在我可以尝试之前,我需要弄清楚如何在 umbraco 中添加新的枚举数据类型或覆盖当前的字符串属性。如果您熟悉 umbraco,我正在使用下拉数据类型(允许字符串预值)和自动生成以下内容的模型构建器 /// /// 位置:播放器的主要位置。 /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder", "8.1.0")] [ImplementPropertyType("position")] public string Position => this.Value("位置");
    • 抱歉,恐怕我的 Umbraco 经验很少。
    • 没问题,我想办法。最重要的方面是我了解 groupBy 键的排序,现在更清楚了。现在@Dmitry 解决方案有效,但我确实喜欢枚举方法,感觉更干净。我会进一步研究它。谢谢
    【解决方案3】:

    您可以使用枚举代替字符串来表示玩家位置,这样在引擎盖下它们将被视为 int。

    public enum PlayerPosition
    {
        goalkeeper, defender, midfielder, striker
    }
    

    当我在控制台应用程序中尝试它时,如果您在运行 group by 之前运行 order by,它似乎可以工作。

    var players = new List<Player>
            {
                new Player { Name = "A", Position = PlayerPosition.midfielder },
                new Player { Name = "B", Position = PlayerPosition.goalkeeper },
                new Player { Name = "C", Position = PlayerPosition.defender },
                new Player { Name = "D", Position = PlayerPosition.midfielder },
                new Player { Name = "E", Position = PlayerPosition.striker },
            };
    
    var result = players.OrderBy(x => x.Position).GroupBy(x => x.Position);
    

    【讨论】:

    • 嘿山姆,这感觉类似于 Johnathans 的回答。我相信它会起作用,但我需要弄清楚如何在 Umbraco 中添加它。我确实喜欢 enum 方法,它也保持了强类型化的感觉 :)
    • 我在输入之前没有看到他的答案,与他的建议基本相同。唯一的区别是 groupby 和 order by 语句的顺序。对不起,我和 Umbraco 没什么关系
    【解决方案4】:

    您可以从playerGroupsSortOrder 创建一个位置订单Dictionary&lt;string, int&gt;,然后使用此查找字典对您的玩家列表进行分组和排序以进行 O(1) 次查找:

    var playerGroupsSortOrder = new List<string>() 
    { 
        "goalkeeper", 
        "defender", 
        "midfielder", 
        "striker" 
    };
    
    // Create this lookup dictionary here
    var playerPositionOrder = playerGroupsSortOrder
        .Select((pos, index) => (pos, index))
        .ToDictionary(kvp => kvp.pos, kvp => kvp.index);
    
    var sortedGroupPlayerList = teamPlayersLandingPage
        .Children<TeamPlayerPage>()
        .GroupBy(x => x.Position)
        .OrderBy(group => playerPositionOrder[group.Key]);
    

    或者只是以这本字典开头:

    var playerPositionOrder = new Dictionary<string, int>
    {
        { "goalkeeper", 0 },
        { "defender", 1 },
        { "midfielder", 2 },
        { "striker", 3 }
    };
    
    var sortedGroupPlayerList = teamPlayersLandingPage
        .Children<TeamPlayerPage>()
        .GroupBy(x => x.Position)
        .OrderBy(group => playerPositionOrder[group.Key]);
    

    【讨论】:

    • 感谢您的回答,使用字典也很有意义,我会研究一下并尝试所有有用的答案:)。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    • 2015-05-12
    • 2016-05-24
    • 1970-01-01
    相关资源
    最近更新 更多