【问题标题】:C# Ranking of objects, multiple criteriaC# 对象排名,多个条件
【发布时间】:2013-01-22 07:16:22
【问题描述】:

我正在为我编写的 LAN 派对网站构建一个插件,该插件将允许使用循环锦标赛。

一切顺利,但我对根据两个标准进行排名的最有效方法有一些疑问。

基本上,我想要以下排名布局:

         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 Server 中,我会使用:

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

现在,我只有 List、Dictionary 等可以使用

具体来说:

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;

【问题讨论】:

标签: c# linq linq-to-objects ranking


【解决方案1】:

排名并不难。只需将 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

【讨论】:

  • 不错!我所要做的就是在我的班级中实施适当的运算符等,我可以按任何排名!哇!
【解决方案2】:

假设您有一个List&lt;Result&gt; 结构,其中Result 对象具有以下参数...

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

您可以编写一个自定义比较器,然后将其传递给List.Sort(Comparison&lt;Result&gt; comparison)

或者,你可以让你的 Result 对象实现 IComparable&lt;Result&gt; 把它贴在你的课堂上。

        #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&lt;Result&gt;.Sort();

【讨论】:

  • 不明白为什么这被否决了。您可以简单地实现自己的比较器,使用自己的公式来确定两个对象的相等性。任何公式都可以!
  • 我没有投反对票,但我需要非密集排名,这在您的解决方案中不可用。
  • +1:啊,既然我已经清理了,我已经添加了一个类似于你的类,现在我知道这有什么用处了。请参阅问题,了解我最终是如何使用所有建议的。
【解决方案3】:

这应该适用于非密集等级:

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

【讨论】:

  • +1 几乎完美!我认为我希望将参数排序的逻辑放在一个地方(而不是在 order by 子句和 if 语句中。)
  • 可能的错误修复:将“lastRank”设置为 1,以便万一最好成绩为“-1”,排名仍将设置为 1。
  • 排序逻辑集中在一处。比较的逻辑在另一个。您可以使用 CompareTo 和 Equals 将其放入 Result 类
  • @John Gietzen - 赞。已应用修复。
【解决方案4】:

这可能是一个开始:

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

【讨论】:

  • 这是一个很好的开始。我看看能不能添加平局情况
  • 是的,很好的开始 +1。但是,我需要非密集排名。我构建了一个 NUnit 测试来定位所需的行为。很快就会回来查看。
  • 在下面添加了非密集解决方案。本来打算编辑这个,但我认为它是如此不同,需要一个新的答案。
【解决方案5】:

我意识到我参加聚会迟到了,但无论如何我都想试一试。

这是一个专门使用 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; }
}

【讨论】:

    猜你喜欢
    • 2019-02-04
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 2016-10-02
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    相关资源
    最近更新 更多