根据列表中的值设置排名

时间:2017-01-26 17:18:37

标签: c# algorithm linq sorting

我正在努力寻找更好的方法来解决我的问题。 我有一个属性列表。一些属性应排在"排名" (谁更好)。

以下是数据结构:

public class MovieRankData
{
    public Guid MovieId { get; set; }
    public RankData TotalViews { get; set; }
    public RankData TotalMoney { get; set; }
}

public class RankData
{
    public string Name { get; set; }
    public double? Value { get; set; }
    public int Rank { get; set; }
}

非常简单的代码(请参阅评论):

var movieRankData = new List<MovieRankData>
{
    new MovieRankData
    {
        MovieId = Guid.NewGuid(),
        TotalViews = new RankData {Name = "Total Movie View", Value = 985 },    // Rank should be 2 (of 3)
        TotalMoney = new RankData {Name = "Total Movie Money", Value = 525.12}, // Rank should be 1 (of 3)
    },

    new MovieRankData
    {
        MovieId = Guid.NewGuid(),
        TotalViews = new RankData {Name = "Total Movie View", Value = 812},  // Rank should be 3 (of 3)
        TotalMoney = new RankData {Name = "Total Movie Money", Value = 100}, // Rank should be 3 (of 3)
    },

    new MovieRankData
    {
        MovieId = Guid.Empty,
        TotalViews = new RankData {Name = "Total Movie View", Value = 1000}, // Rank should be 1 (of 3)
        TotalMoney = new RankData {Name = "Total Movie Money", Value = 321}, // Rank should be 2 (of 3)
    }
};

我提出了丑陋的foreach,许多临时变量来比较和设置Rank属性。

通过每个元素,将其与其他元素进行比较并对其进行评分的更好方法是什么?

感谢您的建议!

5 个答案:

答案 0 :(得分:0)

参见BotondBalázs对更简单解决方案的回答

继承IComparable接口并根据需要实现CompareTo方法。 例如:

class MovierankData:IComparable
{
    public int CompareTo(MovierankData obj)
    {
        if(this.Value > obj.Value)
        {
            return 1;
        }
        else if(this.Value < obj.Value)
        {
            return -1;
        }
        else
        {
            return this.Name.CompareTo(obj.Name);
        }
    }
}

您现在可以调用yourlist.Sort()并按值排序,如果值相同,则按名称排序。

答案 1 :(得分:0)

您可以实现一个自定义IComparer,用于比较MovieRankData元素并使用Lists Sort方法。

在此处查看示例(请参阅第二个答案): LINQ Custom Sort

答案 2 :(得分:0)

我将如何做到这一点:

var byTotalViews = movieRankData.OrderByDescending(m => m.TotalViews.Value).ToList();
var byTotalMoney = movieRankData.OrderByDescending(m => m.TotalMoney.Value).ToList();

foreach (var movie in movieRankData)
{
    movie.TotalViews.Rank = byTotalViews.IndexOf(movie) + 1;
    movie.TotalMoney.Rank = byTotalMoney.IndexOf(movie) + 1;
}

虽然这不会处理nullTotalViews的{​​{1}}值。如果你告诉我在这种情况下该怎么做,我会修改我的答案。我目前的解决方案将最高等级分配给TotalMoney,因为可以进行可比较的比较。

合理的解决方案是让null也可以为空,如果Ranknull,则将其设置为Value

编辑:也请在评论中阅读@ GiladGreen的建议,以提高绩效。

编辑2:这里是基于字典的版本,可以在大型集合中表现良好。首先,我们定义一个函数来计算排名并将它们存储在字典中:

null

然后我们使用该函数生成private static Dictionary<MovieRankData, int> CalculateRanks( List<MovieRankData> movieRankData, Func<MovieRankData, double?> orderKeySelector) { return movieRankData .OrderByDescending(orderKeySelector) .Select((item, index) => new { item, index }) .ToDictionary(e => e.item, e => e.index + 1); } byTotalViews词典:

byTotalMoney

答案 3 :(得分:0)

在下文中,如果index 1最低,则rank + 1是rank的值。

movieRankData.OrderBy(t => t.TotalViews.Value);
movieRankData.OrderBy(t => t.TotalMoney.Value);

或者rank 1是最高的:

movieRankData.OrderByDesc(t => t.TotalViews.Value);
movieRankData.OrderByDesc(t => t.TotalMoney.Value);

在任何一种情况下,您都可以使用for looprank设置为index + 1中object的{​​{1}}

collection

答案 4 :(得分:0)

在orderby结果上使用带有索引的select重载,您只需循环每个集合一次。为了可维护性,可以使用辅助函数来设置特定属性的等级。这将带来额外的好处,如果添加额外的排名,可以为该排名添加函数调用:

void SetRank(IEnumerable<MovieRankData> list, Func<MovieRankData,RankData> prop)
{
    foreach(var ri in list.Select(prop).OrderByDescending(r=>r.Value).Select((r,i)=> new{r,i}))
            ri.r.Rank = ri.i + 1;
}

用法:

SetRank(movieRankData, m => m.TotalViews);
SetRank(movieRankData, m => m.TotalMoney);

(或者保持要在lambda数组中调用的属性并为每个调用函数)