Linq – 每个组的最高价值
我怎样才能使用Linq从每个组中selectTop值?
当我有一个代码段如:
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} };
预期结果:
球队名称球员名字得分 Srilanka Jayasurya 433 英国colingwood 421 澳大利亚克拉克334
我的答案与Yuriy类似,但使用MaxBy
的MaxBy ,不需要用ints完成比较:
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); }
请注意,我已经将types名称从“Team”更改为“Player”,因为我相信它更有意义 – 您不必从一组球队开始,而是从一组球员开始。
以下代码获取所需的值:
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); }
这将要求您按球队名称进行分组,然后select最高分数。
唯一棘手的部分是获得相应的球员,但不是太糟糕。 只需select最高分的玩家。 粗糙的,如果多个玩家可能有相同的分数,则使用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 };
仅供参考 – 我已经运行这个代码对您的数据,它的工作(我修复之后,一个小数据错误)。
我会使用这个Lambdaexpression式:
IEnumerable<Team> topsScores = teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
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 };
我build议你先在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)。
编译可能会有一些细微的修改。