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 .
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.
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);
}
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).
Je voudrais utiliser cette expression Lambda:
IEnumerable<Team> topsScores =
teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
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
};