2009-10-15 7 views
35

Comment puis-je utiliser LINQ pour sélectionner la valeur Haut de chaque groupeLinq - Valeur Haut 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 souhaité:

 

Team Name   Player Name  Score 
 
Srilanka   Jayasurya  433 

England   colingwood  421 

Australia   Clark   334 

Répondre

26

Ma réponse est semblable à Yuriy de, mais en utilisant MaxBy de MoreLINQ, qui ne nécessite pas la comparaison à faire 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 du type de « Team » pour "Player" car je crois que cela a plus de sens - vous ne commencez pas avec une collection d'équipes, vous commencez avec une collection de joueurs.

+0

bien dit joueurs jon serait le nom approprié. :) – user190560

+0

Plus de lien est un ensemble séparé? – user190560

+0

Je crains que vous deviez changer "player.TeamName" au lieu de "team.TeamName", n'est-ce pas? – user190560

26

Le code suivant obtient la valeur désiré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); 
} 

Il exige l'extension suivante que j'ai écrit 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; 
    } 
} 

Voici 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); 
} 
+0

Merci beaucoup Yuriy pour montrer approche différente – user190560

+0

Vous avez oublié de joindre GetEnumerator() en utilisant() bloquer – Yurik

+0

@Yurik fixe.Je déteste les limites. –

-1

Je vous suggère d'abord mettre en œuvre une méthode d'extension la classe IEnumerbale appelée Top Par exemple:

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

Ensuite, vous pouvez écrire:

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

Il pourrait y avoir de légères modifications pour le compiler.

12

Cela vous demandera de grouper par nom d'équipe puis de sélectionner le score maximum.

La seule difficulté est d'obtenir le lecteur correspondant, mais ce n'est pas si mal. Sélectionnez simplement le joueur avec le score maximum. De grossier, s'il est possible pour plus d'un joueur d'avoir 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 - Je ne lance ce code contre vos données et cela a fonctionné (après que je fixe une, petite erreur de données).

+0

Merci beaucoup – user190560

0

La mise en œuvre proposée par The Lame Duck est excellente, mais nécessite deux passages O (n) sur l'ensemble groupé pour déterminer le Max. Il serait avantageux de calculer MaxScore une fois et ensuite de réutiliser. C'est où SelectMany (le mot-clé let en C#) est très pratique. 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 
     }; 
7

J'utiliser cette expression Lambda:

IEnumerable<Team> topsScores = 
teams.GroupBy(x => x.TeamName).Select(t => t.OrderByDescending(c => c.PlayerScore).FirstOrDefault()); 
Questions connexes