2009-05-31 20 views
9

Estoy construyendo un complemento para un sitio web de LAN que escribí que permitiría el uso de un torneo de Round Robin.C# Ranking de objetos, múltiples criterios

Todo va bien, pero tengo algunas preguntas sobre la forma más eficiente de clasificar más de dos criterios.

Básicamente, me gustaría que el diseño siguiente clasificación:

  Rank Wins TotalScore 
PersonE 1  5  50 
PersonD 2  3.5 37 
PersonA 2  3.5 37 
PersonC 4  2.5 26 
PersonB 5  2.5 24 
PersonF 6  0  12 

En el servidor SQL, me gustaría utilizar:

SELECT 
    [Person], 
    RANK() OVER (ORDER BY Wins DESC, TotalScore DESC) [Rank], 
    [Wins], 
    [TotalScore] 

Ahora, sólo tengo lista, diccionario, y etc, para trabajar con

Específicamente:

Dictionary<TournamentTeam, double> wins = new Dictionary<TournamentTeam, double>(); 
Dictionary<TournamentTeam, double> score = new Dictionary<TournamentTeam, double>(); 

¿Hay alguna manera de hacer este estilo de clasificación con LINQ?

Si no es así, ¿hay una extensible manera que me permitiera más tarde para tomar en cuenta Ganar-Pérdida-Draw en lugar de sólo gana si decido?

Editar:

Mi adaptación de la respuesta de TheSoftwareJedi:

private class RRWinRecord : IComparable 
{ 
    public int Wins { get; set; } 
    public int Losses { get; set; } 
    public int Draws { get; set; } 
    public double OverallScore { get; set; } 
    public double WinRecord 
    { 
     get 
     { 
      return this.Wins * 1.0 + this.Draws * 0.5 + this.Losses * 0.0; 
     } 
    } 

    public int CompareTo(object obj) { ... } 

    public override bool Equals(object obj) { ... } 
    public override int GetHashCode() { ... } 
    public static bool operator ==(RRWinRecord lhs, RRWinRecord rhs) { ... } 
    public static bool operator !=(RRWinRecord lhs, RRWinRecord rhs) { ... } 
    public static bool operator >(RRWinRecord lhs, RRWinRecord rhs) { ... } 
    public static bool operator <(RRWinRecord lhs, RRWinRecord rhs) { ... } 
    public static bool operator >=(RRWinRecord lhs, RRWinRecord rhs) { ... } 
    public static bool operator <=(RRWinRecord lhs, RRWinRecord rhs) { ... } 
} 

... 

    int r = 1, lastRank = 1; 
    RRWinRecord lastRecord = null; 

    var ranks = from team in records.Keys 
       let teamRecord = records[team] 
       orderby teamRecord descending 
       select new RRRank() { Team = team, Rank = r++, Record = teamRecord }; 

    foreach (var rank in ranks) 
    { 
     if (rank.Record != null && lastRecord == rank.Record) 
     { 
      rank.Rank = lastRank; 
     } 

     lastRecord = rank.Record; 
     lastRank = rank.Rank; 

     string scoreDescription = String.Format("{0}-{1}-{2}", rank.Record.Wins, rank.Record.Losses, rank.Record.Draws); 
     yield return new TournamentRanking(rank.Team, rank.Rank, scoreDescription); 
    } 

    yield break; 
+0

, gracias a todas las grandes respuestas chicos! –

Respuesta

2

Esto debería funcionar para un rango no denso:

static class Program 
{ 

    static IEnumerable<Result> GetResults(Dictionary<TournamentTeam, double> wins, Dictionary<TournamentTeam, double> scores) 
    { 
     int r = 1; 
     double lastWin = -1; 
     double lastScore = -1; 
     int lastRank = 1; 

     foreach (var rank in from name in wins.Keys 
          let score = scores[name] 
          let win = wins[name] 
          orderby win descending, score descending 
          select new Result { Name = name, Rank = r++, Score = score, Win = win }) 
     { 
      if (lastWin == rank.Win && lastScore == rank.Score) 
      { 
       rank.Rank = lastRank; 
      } 
      lastWin = rank.Win; 
      lastScore = rank.Score; 
      lastRank = rank.Rank; 
      yield return rank; 
     } 
    } 
} 

class Result 
{ 
    public TournamentTeam Name; 
    public int Rank; 
    public double Score; 
    public double Win; 
} 
+0

+1 ¡Casi perfecto! El que creo que desearía sería que la lógica para ordenar parámetros estuviese en un solo lugar (en lugar de en la cláusula order by, y en if). –

+0

Posible corrección de errores: establezca "lastRank" en 1 para que en caso de que la mejor puntuación de alguna manera resulta ser "-1", el rango se establecerá en 1. –

+0

la lógica para ordenar está en un solo lugar. la lógica para comparar está en otro. podría ponerlo en la clase Result usando CompareTo e Igual – TheSoftwareJedi

1

Esto podría ser un comienzo:

Dictionary<TournamentTeam, double> wins = new Dictionary<TournamentTeam, double>(); 
Dictionary<TournamentTeam, double> score = new Dictionary<TournamentTeam, double>(); 
Dictionary<TournamentTeam, int> ranks = new Dictionary<TournamentTeam, int>(); 

int r = 1; 

ranks = (
    from name 
    in wins.Keys 
    orderby wins[name] descending, scores[name] descending 
    select new { Name = name, Rank = r++ }) 
    .ToDictionary(item => item.Name, item => item.Rank); 
+0

Es un gran comienzo. Veré si no puedo agregar la situación de empate – TheSoftwareJedi

+0

Sí, excelente inicio +1. Sin embargo, necesito una clasificación no densa. Estoy construyendo una prueba NUnit para apuntar al comportamiento deseado. Volveremos pronto. –

+0

Se agregó la solución no densa a continuación. Iba a editar esto, pero creo que es tan diferente que justificaba una nueva respuesta. – TheSoftwareJedi

3

suponiendo que tiene una estructura List<Result> donde el objeto Result tiene la siguientes parámetros ...

Pesron  - string 
Rank  - int 
Wins  - double 
TotalScore - int 

Se podría escribir un comparador personalizado y, a continuación, pasar a que List.Sort(Comparison<Result> comparison)

alternativa, usted podría hacer que su objeto implementar ResultIComparable<Result> y pegue esto en su clase.

 #region IComparable Members 

     public int CompareTo(Result obj) 
     { 
      if (this.Rank.CompareTo(obj.Rank) != 0) 
       return this.Rank.CompareTo(obj.Rank); 

      if (this.Wins.CompareTo(obj.Wins) != 0) 
       return (this.Wins.CompareTo(obj.Wins); 

      return (this.TotalScore.CompareTo(obj.TotalScore) ; 

     } 

     #endregion 

Luego puede llamar al List<Result>.Sort();

+0

No entiendo por qué esto es rechazado. Simplemente puede implementar su propio comparador, con su propia fórmula para determinar la igualdad de dos objetos. ¡Cualquier fórmula servirá! –

+0

gracias eric. Me preguntaba lo mismo –

+0

No voté esto, pero necesito una clasificación no densa, que no está disponible en su solución. –

1

Soy consciente de que estoy tarde a la fiesta, pero quería tomar una foto de todos modos.

Aquí es una versión que utiliza exclusivamente LINQ:

private IEnumerable<TeamRank> GetRankings(Dictionary<TournamentTeam, double> wins, Dictionary<TournamentTeam, double> scores) 
{ 
    var overallRank = 1; 

    return 
     from team in wins.Keys 
     group team by new { Wins = wins[team], TotalScore = scores[team] } into rankGroup 
     orderby rankGroup.Key.Wins descending, rankGroup.Key.TotalScore descending 
     let currentRank = overallRank++ 
     from team in rankGroup 
     select new TeamRank(team, currentRank, rankGroup.Key.Wins, rankGroup.Key.TotalScore); 
} 

El tipo de retorno:

public class TeamRank 
{ 
    public TeamRank(TournamentTeam team, int rank, double wins, double totalScore) 
    { 
     this.Team = team; 
     this.Rank = rank; 
     this.Wins = wins; 
     this.TotalScore = totalScore; 
    } 

    public TournamentTeam Team { get; private set; } 

    public int Rank { get; private set; } 

    public double Wins { get; private set; } 

    public double TotalScore { get; private set; } 
} 
+0

Me gusta esto también. –

12

Clasificación no es demasiado difícil. Solo mezcle ordenar por orden y seleccionar patrones de implementación juntos y puede tener un método de extensión de clasificación fácil de usar.De esta manera:

public static IEnumerable<U> Rank<T, TKey, U> 
    (
     this IEnumerable<T> source, 
     Func<T, TKey> keySelector, 
     Func<T, int, U> selector 
    ) 
    { 
     if (!source.Any()) 
     { 
      yield break; 
     } 

     int itemCount = 0; 
     T[] ordered = source.OrderBy(keySelector).ToArray(); 
     TKey previous = keySelector(ordered[0]); 
     int rank = 1; 
     foreach (T t in ordered) 
     { 
      itemCount += 1; 
      TKey current = keySelector(t); 
      if (!current.Equals(previous)) 
      { 
       rank = itemCount; 
      } 
      yield return selector(t, rank); 
      previous = current; 
     } 
    } 

Aquí hay algo de código de prueba

string[] myNames = new string[] 
{ "Bob", "Mark", "John", "Jim", "Lisa", "Dave" }; 
// 
var query = myNames.Rank(s => s.Length, (s, r) => new { s, r }); 
// 
foreach (var x in query) 
{ 
    Console.WriteLine("{0} {1}", x.r, x.s); 
} 

que produce estos resultados:

1 Bob 
1 Jim 
3 Mark 
3 John 
3 Lisa 
3 Dave 
+0

¡Agradable! Todo lo que tengo que hacer es implementar los operadores adecuados en mi clase, etc. ¡y puedo clasificar por cualquier cosa! ¡cortejar! –

+0

Siempre es bueno tener un "¡cortejo!" :) –

+0

+1 para el enfoque genérico – thmshd

Cuestiones relacionadas