2009-05-31 13 views
9

Buduję wtyczkę dla strony internetowej LAN, którą napisałem, która pozwoliłaby na użycie turnieju Round Robin.C# Ranking obiektów, wiele kryteriów

Wszystko idzie dobrze, ale mam kilka pytań na temat najbardziej efektywnego sposobu na uszeregowanie dwóch kryteriów.

Zasadniczo chciałbym następujący układ: rankingową

  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 

W serwerze SQL, użyłbym:

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

Teraz mam tylko listy, słownik i itp pracować z

szczególności:

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

Czy istnieje sposób na zrobienie tego stylu klasyfikacji w LINQ?

Jeśli nie, czy istnieje rozszerzalny sposób, który pozwoli mi później wziąć na konto Win-Loss-Draw zamiast po prostu wygrywa, jeśli zdecyduję się?

Edit:

Moja adaptacja TheSoftwareJedi za odpowiedź:

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

Dzięki za wszystkie wielkie odpowiedzi chłopaki! –

Odpowiedz

2

To powinno działać dla non-gęstej rankingu:

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 Prawie perfekcyjnie! Wydaje mi się, że chciałbym, aby logika do zamawiania parametrów znajdowała się w jednym miejscu (zamiast w klauzuli order if oraz w instrukcji if). –

+0

Możliwe usunięcie błędu: ustaw "lastRank" na 1, aby w przypadku najlepszy wynik jakoś się okazuje być "-1", pozycja nadal będzie ustawiona na 1. –

+0

logika zamawiania jest w jednym miejscu. logika porównywania jest w innej. możesz umieścić go w klasie wyników używając CompareTo i Equals – TheSoftwareJedi

1

To może być początek:

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

To świetny początek. Zobaczę, czy nie mogę dodać sytuacji dotyczącej remisu. – TheSoftwareJedi

+0

Tak, świetny start +1. Jednak potrzebuję nie-gęstego rankingu. Buduję test NUnit, aby celować w pożądane zachowanie. Wkrótce się odezwę. –

+0

Dodano poniżej gęste rozwiązanie. Miałem właśnie to edytować, ale myślę, że jest tak odmienny, że uzasadnia nową odpowiedź. – TheSoftwareJedi

3

Zakładając masz List<Result> strukturę gdzie obiekt Result ma następujące parametry ...

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

Można napisać niestandardowy comparer, a następnie przekazać, że do List.Sort(Comparison<Result> comparison)

Alternatywa, można po prostu uczynić przedmiot Result wdrożyć IComparable<Result> i trzymać to w swojej klasie.

 #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 

Następnie można po prostu zadzwonić pod numer List<Result>.Sort();

+0

Nie rozumiem, dlaczego jest to odrzucone. Możesz po prostu zaimplementować własnego porównywalnika, z własną formułą, aby określić równość dwóch obiektów. Każda formuła zrobi! –

+0

dzięki eric. zastanawiałem się sam/sama –

+0

Nie głosowałem na to, ale potrzebuję nie-gęstego rankingu, który jest niedostępny w twoim rozwiązaniu. –

1

Zdaję sobie sprawę, że jestem późno do partii, ale chciałem zrobić zdjęcie tak czy owak.

Oto wersja, która wykorzystuje wyłącznie 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); 
} 

Zwracany typ:

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

Ja też to lubię. –

12

Ranking nie jest zbyt trudne. Po prostu mashmash OrderBy i wybierz wzory realizacji razem i możesz mieć łatwą w użyciu metodę przedłużania rankingu.Tak:

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

Oto niektóre kodu testu

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

co daje wyniki:

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

Nice! Wszystko, co muszę zrobić, to zaimplementować odpowiednich operatorów na mojej klasie itp. I mogę dowolnie pozycjonować! zabiegać! –

+0

Zawsze dobrze jest uzyskać "woo!" :) –

+0

+1 dla podejścia ogólnego – thmshd