【问题标题】:Linq - Top value from each groupLinq - 每组的最高值
【发布时间】:2009-10-15 21:52:21
【问题描述】:

如何使用 Linq 从每个组中选择最高值

当我有这样的代码段时:

var teams = new Team[]
 { 
  new Team{PlayerName="Ricky",TeamName="Australia", PlayerScore=234},
  new Team{PlayerName="Hussy",TeamName="Australia", PlayerScore=134},
  new Team{PlayerName="Clark",TeamName="Australia", PlayerScore=334},

  new Team{PlayerName="Sankakara",TeamName="SriLanka", PlayerScore=34},
  new Team{PlayerName="Udana",TeamName="SriLanka", PlayerScore=56},
  new Team{PlayerName="Jayasurya",TeamName="SriLanka", PlayerScore=433},

 new Team{PlayerName="Flintop",TeamName="England", PlayerScore=111},
 new Team{PlayerName="Hamirson",TeamName="England", PlayerScore=13},
 new Team{PlayerName="Colingwood",TeamName="England", PlayerScore=421}
 };

期望的结果:

球队名称 球员姓名 得分 斯里兰卡查亚苏里亚 433 英格兰科林伍德 421 澳大利亚克拉克 334

【问题讨论】:

    标签: c# linq lambda


    【解决方案1】:

    我的回答与 Yuriy 的类似,但使用来自MoreLINQMaxBy,这不需要通过整数进行比较:

    var query = from player in players
                group player by player.TeamName into team
                select team.MaxBy(p => p.PlayerScore);
    
    foreach (Player player in query)
    {
        Console.WriteLine("{0}: {1} ({2})",
            player.TeamName,
            player.PlayerName,
            player.PlayerScore);
    }
    

    请注意,我已将类型名称从“团队”更改为“玩家”,因为我认为这更有意义 - 你不是从一组团队开始,而是从一组玩家开始。

    【讨论】:

    • 说得好,乔恩球员是合适的名字。:)
    • 更多链接是一个单独的程序集?
    • 我担心您必须更改“player.TeamName”而不是“team.TeamName”,不是吗?
    • 谢谢,是的 - 已修复。是的,MoreLINQ 是一个单独的程序集——它是一个具有有用 LINQ 运算符的开源项目。请参阅链接页面。
    • 哇,我从未听说过 MaxBy()。谢谢!
    【解决方案2】:

    以下代码获取所需的值:

    foreach (Team team in teams
        .GroupBy(t => t.TeamName)
        .Select(ig => ig.MaxValue(t => t.PlayerScore)))
    {
        Console.WriteLine(team.TeamName + " " + 
            team.PlayerName + " " + 
            team.PlayerScore);
    }
    

    它需要我今天早些时候写的以下扩展:

    public static T MaxValue<T>(this IEnumerable<T> e, Func<T, int> f)
    {
        if (e == null) throw new ArgumentException();
        using(var en = e.GetEnumerator())
        {
            if (!en.MoveNext()) throw new ArgumentException();
            int max = f(en.Current);
            T maxValue = en.Current;
            int possible = int.MaxValue;
            while (en.MoveNext())
            {
                possible = f(en.Current);
                if (max < possible)
                {
                    max = possible;
                    maxValue = en.Current;
                }
            }
            return maxValue;
        }
    }
    

    以下得到不带扩展名的答案,但速度稍慢:

    foreach (Team team in teams
        .GroupBy(t => t.TeamName)
        .Select(ig => ig.OrderByDescending(t => t.PlayerScore).First()))
    {
        Console.WriteLine(team.TeamName + " " + 
            team.PlayerName + " " + 
            team.PlayerScore);
    }
    

    【讨论】:

    • 您忘记将 GetEnumerator() 包含在 using() 块中
    • 感谢您展示OrderBy 方式。我讨厌不必要的扩展。
    【解决方案3】:

    这将要求您按团队名称分组,然后选择最高分数。

    唯一棘手的部分是获得相应的播放器,但还不错。只需选择得分最高的球员。粗略地说,如果多个玩家有可能获得相同的分数,请使用如下所示的 First() 函数而不是 Single() 函数。

    var x =
        from t in teams
        group t by t.TeamName into groupedT
        select new
        {
            TeamName = groupedT.Key,
            MaxScore = groupedT.Max(gt => gt.PlayerScore),
            MaxPlayer = groupedT.First(gt2 => gt2.PlayerScore == 
                        groupedT.Max(gt => gt.PlayerScore)).PlayerName
        };
    

    仅供参考 - 我确实针对您的数据运行了此代码,并且它有效(在我修复了那个之后,小数据错误)。

    【讨论】:

      【解决方案4】:

      我会使用这个 Lambda 表达式:

      IEnumerable<Team> topsScores = 
      teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
      

      【讨论】:

        【解决方案5】:

        The Lame Duck 提出的实现很棒,但需要两次 O(n) 遍历分组集才能计算出最大值。它会受益于一次计算 MaxScore 然后重用。这就是 SelectMany(C# 中的 let 关键字)派上用场的地方。这是优化后的查询:

        var x = from t in teams 
                group t by t.TeamName into groupedT 
                let maxScore = groupedT.Max(gt => gt.PlayerScore)
                select new 
                { 
                   TeamName = groupedT.Key,
                   MaxScore = maxScore, 
                   MaxPlayer = groupedT.First(gt2 => gt2.PlayerScore == maxScore).PlayerName 
                };
        

        【讨论】:

          【解决方案6】:

          我建议你首先在 IEnumerbale 类上实现一个名为 Top 的扩展方法 例如:

          IEnumerable<T,T1> Top(this IEnumerable<T> target, Func<T1> keySelector, int topCount)
          {
              return target.OrderBy(i => keySelector(i)).Take(topCount);
          }
          

          然后你可以写:

          teams.GroupBy(team => team.TeamName).Top(team => team.PlayerScore, 1)。

          可能需要稍作修改才能使其编译。

          【讨论】:

            猜你喜欢
            • 2022-11-16
            • 1970-01-01
            • 2015-03-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-11-05
            相关资源
            最近更新 更多