C# Ranking of objects, multiple criteria

旧街凉风 提交于 2019-11-27 20:17:16

This should work for a non-dense rank:

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

Ranking isn't too hard. Just mishmash OrderBy and Select implementation patterns together and you can have an easy to use Ranking extension method. Like this:

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

Here's some test code

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

Which yields these results:

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

Assuming you have a List<Result> structure where the Result object has the following parameters...

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

You could write a custom comparer, and then pass that to List.Sort(Comparison<Result> comparison)

Alternative, you could just make your Result object implement IComparable<Result> and stick this in your class.

        #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

Then you can just call List<Result>.Sort();

This could be a start:

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

I realize I'm late to the party, but I wanted to take a shot anyhow.

Here is a version which uses LINQ exclusively:

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

The return type:

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; }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!