2009-10-15 17 views
35

¿Cómo puedo utilizar LINQ para seleccionar el valor superior de cada grupoLinq - el valor superior de cada grupo

cuando tengo un segmento de código como:

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

Resultado deseado:

 

Team Name   Player Name  Score 
 
Srilanka   Jayasurya  433 

England   colingwood  421 

Australia   Clark   334 

Respuesta

26

Mi respuesta es similar a la de Yuriy, pero utilizando MaxBy de MoreLINQ, que no requiere la comparación que ha hecho por enteros:

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

Tenga en cuenta que he cambiado el nombre del tipo de "Equipo" para "Jugador" porque creo que tiene más sentido; no comienzas con una colección de equipos, comienzas con una colección de jugadores.

+0

bien dicho jon players sería el nombre apropiado :) – user190560

+0

¿Más enlace es un montaje por separado? – user190560

+0

me temo que tiene que cambiar "player.TeamName" en lugar de "team.TeamName", ¿o no? – user190560

26

El siguiente código obtiene el valor deseado:

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

Se requiere la siguiente extensión que escribí el día de hoy:

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

El siguiente obtiene la respuesta sin la extensión, pero es un poco más 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

Muchas gracias Yuriy para mostrar enfoque diferente – user190560

+0

Usted se olvidó de incluir GetEnumerator() en un usando() bloquear – Yurik

+0

@Yurik arreglado.Odio los límites. –

-1

yo sugeriría que primero poner en práctica un método de extensión en la clase IEnumerbale denominada Top Por ejemplo:

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

a continuación, se puede escribir:

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

Puede haber ligeras modificaciones para compilar.

12

Esto requerirá que agrupe por nombre de equipo y luego seleccione la puntuación máxima.

La única parte difícil es conseguir el jugador correspondiente, pero no está tan mal. Simplemente selecciona el jugador con el puntaje máximo. Por supuesto, si es posible que más de un jugador tenga puntajes idénticos, haga esto usando la función Primera() como se muestra a continuación en lugar de la función Única().

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 - Yo he corrido este código contra sus datos y funcionó (después me fijo que uno, pequeño error de datos).

+0

Muchas gracias – user190560

0

La implementación propuesta por The Lame Duck es genial, pero requiere dos O (n) pasa sobre el conjunto agrupado para descubrir el Max. Se beneficiaría al calcular MaxScore una vez y luego reutilizarlo. Aquí es donde SelectMany (la palabra clave let en C#) es útil. Ésta es la consulta optimizada:

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

me gustaría utilizar esta expresión Lambda:

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