【问题标题】:LINQ sorting. How to determine equality for multiple fieldsLINQ 排序。如何确定多个字段的相等性
【发布时间】:2016-09-21 18:08:51
【问题描述】:

我正在使用 LINQ 对多个字段进行排序。但是如何确定列表中的两个或多个条目是否相等?也就是说,所有字段都是相等的,排序顺序不会因为相等而改变。

public List<SfpDb.ResultatViewRang> sortResMest(List<SfpDb.ResultatViewRang> resultat)
{
    return resultat
                    .OrderBy(p => p.statdsq)
                    .ThenBy(p => p.statdns)
                    .ThenBy(p => p.statdnf)
                    .ThenByDescending(p => p.totsum)
                    .ThenByDescending(p => p.totOmskyting)
                    .ThenByDescending(p => p.totinnertreff)
                    .ThenByDescending(p => p.sumr10)
                    .ThenByDescending(p => p.sumr9)
                    .ThenByDescending(p => p.sumr8)
                    .ThenByDescending(p => p.sumr7)
                    .ThenByDescending(p => p.sumr6)
                    .ThenByDescending(p => p.sumr5)
                    .ThenByDescending(p => p.sumr4)
                    .ThenByDescending(p => p.sumr3)
                    .ThenByDescending(p => p.sumr2)
                    .ThenByDescending(p => p.sumr1)
                    .ThenBy(p => p.perTreffRangStr)
                    .ThenBy(p => p.enavn, strCompareNo)
                    .ThenBy(p => p.fnavn, strCompareNo)
                    .ToList<SfpDb.ResultatViewRang>();
}

内容来自保存比赛结果的数据库,并根据比赛规则进行排序。排序用于确定竞争对手获得的位置。

根据规则,如果一个或多个竞争对手在比赛中具有相同的值(p.totsum,p.totOmskyting,......),他们将获得相同的位置。例如:
1. 吉姆·哈里斯,580、25、32,......
2. 汤姆·詹森,560、20、30,......
3. 让·约翰逊,523、23、26 岁,......
3. 罗伊·比曼,523、23、26,......
5. 道格·威尔逊,520、23、26,......

排序后的问题是,在这个例子中,我不确定如何识别位置 3 的两个人。

一般来说,具有以下类:

public class MigrObject
{
    public int first { get; set; }
    public int second { get; set; }
    public int third { get; set; }
}

它将使用排序

.OrderByDescending(p => p.first)
.ThenByDescending(p => p.second)
.ThenByDescending(p => p.third)

有以下记录:(第一、第二、第三)
580、25、32
560、30、30
523、23、26
523、23、26
523、23、26
520、23、26
518、40、30
518、40、30
430、14、16

您如何识别相等的记录?处理完记录后,应该有以下仓位/顺序:
1:580、25、32
2:560、30、30
3:523、23、26
3:523、23、26
3:523、23、26
6:520、23、26
7:518、40、30
7:518、40、30
9:430、14、16

根据国际规则,参与者将按照第一个字段(first)中的值进行排序。如果有平局,将比较第二列,所以我们按“第二”排序。如果还有平局,就会比较第三列,所以我们按“第三”排序。如果仍有平局,则运动员必须具有相同的排名,并且必须使用运动员的姓氏按拉丁字母顺序列出。

如果处理后的位置是 1,2,3,3,3,6,7 还是 1,2,3,3,3,4,5 不重要。 主要问题是在比较所有字段后知道是否有任何联系,以及涉及哪些记录。

我希望 LINQ 中有一些功能可以在所有比较字段相等时通知您。

【问题讨论】:

  • 你可以使用DistinctGroupBy方法。
  • “所有字段都是相等的,排序顺序不会因为相等而改变”——你为什么要关心?你打算如何使用平等?请提供一个很好的minimal reproducible example,清楚地表明您正在尝试做什么。准确而具体地解释该代码的作用以及您希望它做什么。
  • 尝试检查stackoverflow.com/q/312024/3956100 我确定您可以更改 linq 中的 IComparer 以满足您的需要。
  • 你的问题不清楚。据我所知,您需要做的就是获取p.totsum, p.totOmskyting, p.totinnertreff 的总和并按 desc order 排序。您还可以将 order by 添加到“name”,以显示具有相同标记的人是根据字母顺序排序的。
  • 我认为这个问题不正确,因此您希望看到 2 个人在同一位置(在您的示例中为 3 号)。当您查看 LINQ 查询的结果时,可以计算该位置,因此它与 LINQ 查询本身无关。但是,我不明白为什么你在#3 之后的位置是#5,那不应该是#4 吗?

标签: c# linq sorting equality


【解决方案1】:

请试试这个。 我已经测试过了。

public class Program
    {
        public class TestData
        {
            public int First { get; set; }
            public int Second { get; set; }
            public int Third { get; set; }
            public int Place { get; set; }
        }

        public static void Main(string[] args)
        {
            var data = new List<TestData>
            {
                new TestData {First = 580, Second = 25, Third = 32},
                new TestData {First = 560, Second = 30, Third = 30},
                new TestData {First = 523, Second = 23, Third = 26},
                new TestData {First = 523, Second = 23, Third = 26},
                new TestData {First = 523, Second = 23, Third = 26},
                new TestData {First = 520, Second = 23, Third = 26},
                new TestData {First = 518, Second = 40, Third = 30},
                new TestData {First = 518, Second = 40, Third = 30},
                new TestData {First = 430, Second = 14, Third = 16}
            };

            var sorted =
                (from d in data
                    orderby d.First descending, d.Second descending, d.Third descending 
                    group d by d.First.ToString() + d.Second + d.Third into gd
                    select gd.ToList())
                    .ToList();

            var result = new List<TestData>();
            var place = 1;
            foreach (var sd in sorted)
            {
                sd.ForEach(p => p.Place = place);
                result.AddRange(sd);
                place += sd.Count;
            }
        }
    }

【讨论】:

  • 一个小问题 - 如果数字是 { 52, 32, 326 }{523, 23, 26 }d.First.ToString() + d.Second + d.Third 将失败。你也许应该放一个分隔线。类似$"{d.First}:{d.Second}:{d.Third}"
【解决方案2】:

尝试这样的事情:(为简单起见,我使用您的 MigrObject 示例)

Dictionary<MigrObject, int> orderedResult = new Dictionary<MigrObject, int>(); //reverse dictionary where int is the 'order' you want
foreach (var item in resultat) // assuming resultat is a List of MigrObject
{
    int order = 1;
    if(!orderedResult.Any(o => o.first == item.first && o.second == item.second && o.third == item.third)) // I don't use ContainsKey(item) because it may not work for custom defined classes as key
    {
        orderedResult.Add(item, order);
        count++;
    }
    else
        orderedResult.Add(item, order);
}

它可能看起来很复杂,但这里是解释:

-字典是颠倒的,因为我们可以为同一个键有多个值,但不能反过来

-从 1 开始订单,因为没有第 0 位(在您的示例中)

-检查字典是否包含每个项目的记录:如果没有,则添加它并将计数加1,如果是,则不增加计数(从而使2个重复项具有相同的顺序)

-假设resultat 列表已正确排序。

-当你有很多字段要比较时,这个方法可能会变得复杂,但是如果你有更好的比较方法,你可以修改它。

最后您可以遍历字典,每个项目的“位置/顺序”就是它的值,您可以将其添加到数组或您想要的任何数据类型。 (字典除外,因为键必须是唯一的

【讨论】:

  • 感谢您的反馈。你知道你的不能编译并且你不能使用MigrObject作为字典的键吗?
  • 谢谢。我会调查一下。如果我必须遍历列表,这可能会起作用。
【解决方案3】:

尝试这样做:

var scores = new [] { 580, 560, 523, 523, 523, 520, 518, 518, 430, };

var results =
    scores
        .OrderByDescending(x => x) // just in case scores not in order
        .GroupBy(x => x)
        .Aggregate(
            new { Rank = 1, Results = new List<Tuple<int, int>>() },
            (a, gx) =>
            {
                a.Results.AddRange(gx.Select(x => Tuple.Create(a.Rank, x)));
                return new { Rank = a.Rank + gx.Count(), Results = a.Results };
            })
        .Results;

我得到以下结果:

您需要调整代码以适应您的输入。

【讨论】:

  • 如果投反对票的人会回应我很想知道我做错了什么?
  • 我相信问题发布者对他不喜欢的答案投了反对票(你的有效,虽然更复杂,但我的也是)。
  • @Enigmativity :你的作品。不过,您可能需要检查它的性能。
  • @KosalaW - 性能有什么问题?应该相当不错,即使对于大型列表也是如此。
  • 从外观上看,您的阅读量更多。例如:gx.Count(), gx.Select()。我刚刚对您的代码进行了性能测试。大约需要 20 毫秒。而我的大约需要 3 毫秒(这包括将测试数据添加到列表、打印输出等)。因此,对于大型列表,您的代码将出现严重问题。
猜你喜欢
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 2023-04-04
  • 2010-09-17
相关资源
最近更新 更多