web-dev-qa-db-fra.com

Linq - Valeur maximale de chaque groupe

Comment puis-je employer Linq pour sélectionner la valeur supérieure de chaque groupe

quand j'ai un segment de code comme:

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

Résultat désiré :


 Nom de l'équipe Nom du joueur Score 

 Srilanka Jayasurya 433 
.
37
user190560

Ma réponse est semblable à celle de Yuri, mais utilise MaxBy from MoreLINQ , qui n'exige pas que la comparaison soit effectuée par 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);
}

Notez que j'ai changé le nom de type de "Equipe" en "Joueur", car je pense que cela a plus de sens: vous ne commencez pas avec une collection d'équipes, vous démarrez avec une collection de joueurs.

28
Jon Skeet

Le code suivant obtient la valeur souhaitée:

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

Cela nécessite l'extension suivante que j'ai écrite plus tôt aujourd'hui:

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

Ce qui suit obtient la réponse sans l'extension, mais est légèrement plus lent:

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
Yuriy Faktorovich

Cela vous obligera à grouper par nom d’équipe puis à sélectionner le score maximum.

La seule difficulté consiste à trouver le joueur correspondant, mais ce n’est pas si mal. Il suffit de sélectionner le joueur avec le score maximum. Bien sûr, s'il est possible que plusieurs joueurs aient des scores identiques, utilisez la fonction First () comme indiqué ci-dessous plutôt que la fonction 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
    };

FYI - J'ai exécuté ce code sur vos données et cela a fonctionné (après avoir corrigé celui-ci, petite erreur de données).

13
Michael La Voie

Je voudrais utiliser cette expression Lambda:

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

L'implémentation proposée par The Lame Duck est excellente, mais nécessite deux O(n) sur l'ensemble groupé pour déterminer le Max. Il serait avantageux de calculer MaxScore une fois, puis de le réutiliser. C’est là que SelectMany (le mot-clé let en C #) est très utile. Voici la requête optimisée: 

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