C# Ранжирование объектов, несколько критериев

Я создаю плагин для веб-сайта вечеринки в локальной сети, который я написал, который позволит использовать турнир по круговой системе.

Все идет хорошо, но у меня есть несколько вопросов о наиболее эффективном способе ранжирования по двум критериям.

В принципе, я хотел бы следующий макет ранжирования:

         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

В SQL-сервере я бы использовал:

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

Теперь у меня есть только список, словарь и т. д. для работы

Конкретно:

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

Есть ли способ ранжирования в таком стиле с помощью LINQ?

Если нет, есть ли расширяемый способ, который позволил бы мне позже учитывать выигрыш-проигрыш-ничью, а не просто выигрыши, если я захочу?

Изменить:

Моя адаптация ответа 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;

person John Gietzen    schedule 31.05.2009    source источник
comment
Спасибо за все ОТЛИЧНЫЕ ответы, ребята!   -  person John Gietzen    schedule 02.06.2009
comment
Здесь есть простой ответ на этот вопрос: «сортировать коллекцию и ранжировать результат по определенным критериям»> stackoverflow.com/questions/23538512/   -  person Hans Vonn    schedule 25.10.2019


Ответы (5)


Это должно работать для неплотного ранга:

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;
}
person TheSoftwareJedi    schedule 31.05.2009
comment
+1 Почти идеально! Думаю, мне бы хотелось, чтобы логика для упорядочивания параметров находилась в одном месте (а не в предложении order by и в выражении if). - person John Gietzen; 31.05.2009
comment
Возможное исправление ошибки: установите для lastRank значение 1, чтобы в случае, если лучший результат каким-либо образом окажется равным -1, ранг все равно будет равен 1. - person John Gietzen; 31.05.2009
comment
логика заказа в одном месте. логика сравнения в другом. вы можете поместить его в класс Result, используя CompareTo и Equals - person TheSoftwareJedi; 31.05.2009
comment
@Джон Гитцен - респект. применено исправление. - person TheSoftwareJedi; 31.05.2009

Ранжирование не слишком сложно. Просто смешайте вместе шаблоны реализации OrderBy и Select, и вы получите простой в использовании метод расширения Ranking. Как это:

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

Вот тестовый код

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

Что дает следующие результаты:

1 Bob
1 Jim
3 Mark
3 John
3 Lisa
3 Dave
person Amy B    schedule 01.06.2009
comment
Хороший! Все, что мне нужно сделать, это реализовать соответствующие операторы в моем классе и т. д., и я могу ранжировать по чему угодно! ву! - person John Gietzen; 02.06.2009

Предполагая, что у вас есть структура List<Result>, где объект Result имеет следующие параметры...

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

Вы можете написать собственный компаратор, а затем передать его в List.Sort(Comparison<Result> comparison)

В качестве альтернативы вы можете просто реализовать объект Result IComparable<Result> и вставить его в свой класс.

        #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

Тогда вы можете просто позвонить List<Result>.Sort();

person Eoin Campbell    schedule 31.05.2009
comment
Не понимаю, почему за это проголосовали. Вы можете просто реализовать свой собственный компаратор с собственной формулой для определения равенства двух объектов. Подойдет любая формула! - person Erik van Brakel; 31.05.2009
comment
Я не голосовал за это, но мне нужно неплотное ранжирование, которое недоступно в вашем решении. - person John Gietzen; 31.05.2009
comment
+1: А, теперь, когда я очистился, я добавил класс, похожий на ваш, и теперь вижу, где это полезно. См. вопрос о том, как я использовал все предложения. - person John Gietzen; 31.05.2009

Это может быть началом:

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);
person Philippe Leybaert    schedule 31.05.2009
comment
Это отличное начало. Я посмотрю, смогу ли я добавить ситуацию с галстуком - person TheSoftwareJedi; 31.05.2009
comment
Да, отличное начало +1. Однако мне нужно неплотное ранжирование. Я создаю тест NUnit для достижения желаемого поведения. Скоро проверю. - person John Gietzen; 31.05.2009
comment
Добавлено неплотное решение ниже. Собирался просто отредактировать это, но я думаю, что это настолько отличается, что требует нового ответа. - person TheSoftwareJedi; 31.05.2009

Я понимаю, что опаздываю на вечеринку, но я все равно хотел попробовать.

Вот версия, которая использует исключительно 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);
}

Тип возврата:

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; }
}
person Bryan Watts    schedule 31.05.2009