web-dev-qa-db-ja.com

Linq-各グループの最高値

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 
39
user190560

私の答えは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);
}

タイプ名を「チーム」から「プレーヤー」に変更したことに注意してください。これはより理にかなっていると思います。チームのコレクションから始めるのではなく、プレーヤーのコレクションから始めます。

28
Jon Skeet

次のコードは、目的の値を取得します。

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);
}
26

これにより、チーム名でグループ化し、最大スコアを選択する必要があります。

唯一のトリッキーな部分は対応するプレイヤーを取得することですが、それほど悪くはありません。最大スコアのプレーヤーを選択するだけです。大まかに言って、複数のプレーヤーが同じスコアを持つことが可能である場合は、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
    };

参考までに-私はあなたのデータに対してこのコードを実行しました、そしてそれは機能しました(私がそれを修正した後、小さなデータの誤り)。

13
Michael La Voie

私はこのラムダ式を使用します:

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

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 
        };
0
Drew Marsh