2009-10-15 20 views
35

Come posso utilizzare Linq per selezionare il valore superiore di ogni gruppoLinq - Top valore da ciascun gruppo

quando ho un segmento di codice come:

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

Risultato desiderato:

 

Team Name   Player Name  Score 
 
Srilanka   Jayasurya  433 

England   colingwood  421 

Australia   Clark   334 

risposta

26

La mia risposta è simile a Yuriy di, ma usando MaxBy da MoreLINQ, che non richiede il confronto venga fatto dal interi:

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

Nota che ho cambiato il nome del tipo da "Team" a "Giocatore" come credo abbia più senso - non si inizia con una serie di squadre, si inizia con una serie di giocatori.

+0

ben detto che i giocatori jon sarebbero il nome appropriato :) :) – user190560

+0

Più link è un assembly separato? – user190560

+0

temo che tu debba cambiare "player.TeamName" invece "team.TeamName", non è vero? – user190560

26

Il seguente codice ottiene il valore desiderato:

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

Si richiede la seguente estensione che ho scritto in precedenza oggi:

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

Il seguente ottiene la risposta senza l'estensione, ma è leggermente più lento:

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

ringrazio molto Yuriy per mostrare approccio diverso – user190560

+0

Hai dimenticato di racchiudere GetEnumerator() in un utilizzando() bloccare – Yurik

+0

@Yurik risolto.Odio i limiti –

-1

vorrei suggerire di primo implementare un metodo di estensione sulla classe IEnumerbale denominata Top Ad esempio:

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

Poi si può scrivere:

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

Potrebbero esserci alcune lievi modifiche per renderlo compilabile.

12

Questo richiederà di raggruppare per nome squadra quindi selezionare il punteggio massimo.

L'unica parte difficile è ottenere il lettore corrispondente, ma non è male. Basta selezionare il giocatore con il punteggio massimo. Di massima, se è possibile che più di un giocatore abbia punteggi identici, usa la funzione First() come mostrato di seguito piuttosto che la funzione 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 
    }; 

CRONACA - ho eseguito questo codice contro i dati e ha funzionato (dopo mi fisso che uno, pochi dati errore).

+0

Grazie mille – user190560

0

L'implementazione proposta da The Lame Duck è ottima, ma richiede due O (n) passaggi per il gruppo impostato per calcolare il valore massimo. Trarrebbe vantaggio dal calcolo di MaxScore una volta e poi dal riutilizzo. È qui che SelectMany (la parola chiave let in C#) è utile. Ecco la query ottimizzata:

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

Vorrei usare questa espressione lambda:

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