Linq - Top value from each group

前端 未结 6 1798
盖世英雄少女心
盖世英雄少女心 2020-12-03 01:10

How can I employ Linq to select Top value from each group

when I have a code segment like :

var teams = new Team[]
 { 
  new Team{PlayerName=\"Ricky\         


        
相关标签:
6条回答
  • 2020-12-03 01:13

    I would suggest you first implement an extension method on the IEnumerbale class called Top For example:

    IEnumerable<T,T1> Top(this IEnumerable<T> target, Func<T1> keySelector, int topCount)
    {
        return target.OrderBy(i => keySelector(i)).Take(topCount);
    }
    

    Then you can write:

    teams.GroupBy(team => team.TeamName).Top(team => team.PlayerScore, 1).

    There might be some slight modifications to make it compile.

    0 讨论(0)
  • 2020-12-03 01:17

    The following code gets the desired value:

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

    It requires the following extension that I wrote earlier today:

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

    The following gets the answer without the extension, but is slightly slower:

    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);
    }
    
    0 讨论(0)
  • 2020-12-03 01:19

    I would use this Lambda expression:

    IEnumerable<Team> topsScores = 
    teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault());
    
    0 讨论(0)
  • 2020-12-03 01:19

    The implementation proposed by The Lame Duck is great, but requires two O(n) passes over the grouped set to figure out the Max. It would benefit from calculating MaxScore once and then reusing. This is where SelectMany (the let keyword in C#) comes in handy. Here is the optimized query:

    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 讨论(0)
  • 2020-12-03 01:27

    My answer is similar to Yuriy's, but using MaxBy from MoreLINQ, which doesn't require the comparison to be done by 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);
    }
    

    Note that I've changed the type name from "Team" to "Player" as I believe it makes more sense - you don't start off with a collection of teams, you start off with a collection of players.

    0 讨论(0)
  • 2020-12-03 01:34

    This will require you to group by team name then select the max score.

    The only tricky part is getting the corresponding player, but its not too bad. Just select the player with the max score. Of coarse, if its possible for more than one player to have identical scores do this using the First() function as shown below rather than the Single() function.

    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 - I did run this code against your data and it worked (after I fixed that one, little data mistake).

    0 讨论(0)
提交回复
热议问题