2

I have the following collection of scores;

var scores = new[]
    {
      new { score = 10, player = "Dave" }, 
      new { score = 9, player = "Dave" }, 
      new { score = 8, player = "Steve" }, 
      new { score = 7, player = "Pete" }, 
      new { score = 8, player = "Paul" }, 
      new { score = 4, player = "Mike" }
    };

I would like the a collection of scores containing the top 3 players and their high scores (ordered by score, highest score first) i.e.

{ score = 10, player = "Dave" }
{ score = 8, player = "Paul" }
{ score = 8, player = "Steve" }

This won't work;

var top3 = scores.OrderByDescending(s => s.score).Take(3);

This is because Dave has two of the top three scores and would appear twice.

Paul and Steve have the same score, for the purposes of this question I do not care which appears first in the list but there is a bonus up-vote for any solution that puts the person whose name appears first in the alphabet highest in the event that their scores are tied (to return results in the exact order shown in my example results).

The answer should use linq and lambda expressions

Thanks

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
mark_h
  • 5,233
  • 4
  • 36
  • 52
  • What would you want to be returned if three players scored 8, rather than just the two in your example? – Ulric May 12 '16 at 12:44
  • You make a good point. In my real implementation it would still be the top 3 (and then secondary ordering would become vital) but the oldest score would take precedence however my question I obviously didn't include a date field. – mark_h May 12 '16 at 12:52

3 Answers3

6

If a player name should only appear once you have to group the collection by player and order each group by score. Then you can pick the highest 3.

var top3 = scores.GroupBy(x => x.player)
                 .Select(x => x.OrderByDescending(y => y.score).First())
                 .OrderByDescending(x => x.score)
                 .ThenBy(x => x.player)
                 .Take(3);
fubo
  • 44,811
  • 17
  • 103
  • 137
  • This works thanks and you got the additional +1. You were first to answer so you get the correct tick, but I wonder whose answer would be quickest, yours or Ehsans (I tried testing in this trivial example and they both completed so fast it was not enough to call it). When I move this into my real implementation I might try both and see if there is a difference. Thanks for the help – mark_h May 12 '16 at 12:38
  • i hope your real environment isn't a mySQL database http://stackoverflow.com/questions/36786443/entity-framework-groupby-take-the-oldest-with-mysql – fubo May 12 '16 at 12:40
  • SQL Server 2012, accessed via EF6 - thanks for the link – mark_h May 12 '16 at 12:48
4

You can group them and then pick the maximum score of each player, and then order them:

var top3 = scores.GroupBy(x=>x.player)
                 .Select(g=>new 
                         {
                           player=g.Key,
                           score=g.Max(p=>p.score)
                        })
                 .OrderByDescending(s => s.score)
                 .ThenBy(p=>p.player).Take(3);
Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
  • 1
    And for those additional points, you can add **.ThenBy(p => p.player)** before Take(3) – uTeisT May 12 '16 at 12:15
  • we will have unique players after grouping with highest score, sorry, i missed that part of question, @uteist – Ehsan Sajjad May 12 '16 at 12:18
  • Yeap and I upvoted your answer. But in OP the poster says *there is a bonus up-vote for any solution that puts the person whose name appears first in the alphabet* So, if 2 players have same scores, **ThenBy** clause would let him to get the preferred output. – uTeisT May 12 '16 at 12:21
  • agree @uteist i said i missed that part of question, thanks for the reminder oi updated it – Ehsan Sajjad May 12 '16 at 12:23
  • Thanks Ehsan, this works perfectly and only requires a single OrderBy making it quicker in my real implementation – mark_h May 13 '16 at 07:36
1
scores.GroupBy(i => i.player)
      .Select(g => g.OrderByDescending(s => s.score).First())
      .OrderByDescending(i => i.score)
      .ThenBy(i => i.player)
      .Take(3)

but there is a bonus up-vote for any solution that puts the person whose name appears first in the alphabet highest in the event that their scores are tied

ThenBy is used for tie-breaks on ordering.

Idea is to group by player to get each players best score and then process that flat list.

tolanj
  • 3,651
  • 16
  • 30