Подтвердить что ты не робот

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 
4b9b3361

Ответ 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).

Могут быть некоторые незначительные изменения, чтобы скомпилировать его.