Linq - максимальное значение для каждой группы
Как я могу использовать Linq для выбора значения Top из каждой группы
когда у меня есть сегмент кода, например:
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}
};
Желаемый результат:
Team Name Player Name Score
Srilanka Jayasurya 433
England colingwood 421
Australia Clark 334
Ответы
Ответ 1
Мой ответ похож на Yuriy's, но используя MaxBy
из MoreLINQ, который не требует сравнения с помощью 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);
}
Заметьте, что я изменил имя типа из "Team" на "Player", так как я считаю, что это имеет больше смысла - вы не начинаете с набора команд, вы начинаете с коллекции игроков.
Ответ 2
Следующий код получает желаемое значение:
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);
}
Ответ 3
Это потребует, чтобы вы сгруппировали имя команды, затем выберите максимальную оценку.
Единственная сложная часть - получение соответствующего игрока, но это не так уж плохо. Просто выберите игрока с максимальным счетом. Грубо, если это возможно для того, чтобы более одного игрока имели одинаковые баллы, используйте функцию 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
};
FYI - я выполнил этот код против ваших данных, и он работал (после того, как я исправил эту ошибку, небольшую ошибку).
Ответ 4
Я бы использовал это выражение Лямбды:
IEnumerable<Team> topsScores =
teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
Ответ 5
Реализация, предложенная The Lame Duck, велика, но требует, чтобы два о (n) проходили по сгруппированному множеству, чтобы выяснить Макс. Было бы полезно вычислить MaxScore один раз, а затем повторно использовать. Именно здесь удобно использовать SelectMany (ключевое слово 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
};
Ответ 6
Я бы предложил сначала реализовать метод расширения в классе 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).
Могут быть некоторые незначительные изменения, чтобы скомпилировать его.