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
私の答えはYuriyのそれに似ていますが、 MoreLINQ から MaxBy
を使用します。これは、intによる比較を必要としません。
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);
}
タイプ名を「チーム」から「プレーヤー」に変更したことに注意してください。これはより理にかなっていると思います。チームのコレクションから始めるのではなく、プレーヤーのコレクションから始めます。
次のコードは、目的の値を取得します。
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);
}
これにより、チーム名でグループ化し、最大スコアを選択する必要があります。
唯一のトリッキーな部分は対応するプレイヤーを取得することですが、それほど悪くはありません。最大スコアのプレーヤーを選択するだけです。大まかに言って、複数のプレーヤーが同じスコアを持つことが可能である場合は、Single()関数ではなく、次に示すようにFirst()関数を使用してこれを行います。
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
};
参考までに-私はあなたのデータに対してこのコードを実行しました、そしてそれは機能しました(私がそれを修正した後、小さなデータの誤り)。
私はこのラムダ式を使用します:
IEnumerable<Team> topsScores =
teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
The Lame Duckによって提案された実装は素晴らしいですが、グループ化されたセットに対して2つのO(n)パスを渡してMaxを計算する必要があります。ここで、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
};