基于组计数对列表进行排序

本文关键字:排序 列表 于组计 | 更新日期: 2023-09-27 18:36:20

我想对元素计数IGrouping s的List进行排序。

就是这样,理想情况下,列表应该是相同的。 我会妥协一个新的列表,但是元素应该是完全相同的原始对象,而不是副本(无论多么浅),绝对不是匿名对象。

具体来说:我们有一个具有许多属性的实体,以及这种类型的对象列表。 我们希望 (1) 按某些属性(名称、地址等)对对象进行分组,然后 (2) 计算每个组中的元素数量。最后,我们想 (3) 通过首先放置属于较大组的元素,根据这些计数对列表进行重新排序。

注意:我们的主要问题是,我们似乎找不到一种方法来保留对组元素中原始对象的引用。实际上,我们在 Linq 查询中所能选择的只是分组键(或键的属性),而 IGrouping 不会公开任何其他内容。除了查看数据之外,我们也无法弄清楚如何将组元素与列表的元素相关联(即使这样,我们还需要主键,我们不能将其添加到分组键中,否则它会破坏分组的目的开始)。

基于组计数对列表进行排序

var mySortedList = myList.GroupBy(x => x.Name).OrderByDescending(g => g.Count())
                       .SelectMany(x => x).ToList();

在 .NET 中几乎没有任何操作克隆对象。既不深也不浅。LINQ 也不会克隆它处理的元素。因此,一个简单的 LINQ 查询将起作用:

var oldList = ...;
var newList = (from x in oldList
               group x by something into g
               orderby g.Count()
               from x in g //flatten the groups
               select x).ToList();

此代码复制对原始对象的引用。如果你不这么认为,你可能误解了你所看到的。

>嗯,这很尴尬。

我的错误确实是基于一个误解:

class Item
{
    internal string Value { get; set; }
    internal string Qux { get; set; }
    internal string Quux { get; set; }
}
var query = from i in list
            group i by new { i.Value, i.Qux } into g // Note: no Quux, no primary key, just what's necessary
            orderby g.Count() descending, g.Key.Value
            select new { // [1]
                Value = g.Key.Value,
                Qux = g.Key.Qux,
                // Quux?
            }

我的错误假设是 [1] 处的选择作用于单个记录,很像 SQL。好吧,希望我能拯救一个有同样假设的人:事实并非如此。

现在看起来很明显,但是选择作用于单个组,因此在这里将为每个组创建一个新对象,而不是为每个元素创建一个新对象。

我的第二个错误是专注于密钥,想知道如何在不使用它的情况下"传递"其他属性。 我还担心我们似乎被要求制作我们的对象的浅拷贝。 同样,这是基于我们对IGrouping的行为一无所知的事实:由于我们不知道我们正在选择组,我们甚至无法从选择中的每个元素创建新对象。

解决方案绝对不令人印象深刻:

var query = from i in list
            group i by new { i.Value, i.Qux } into g
            orderby g.Count() descending, g.Key.Value
            select g;
foreach (var group in query)
{
    foreach (var item in group)
        Console.WriteLine("{0} {1} {2} [Original object? {3}]",
            item.Value,
            item.Qux,
            item.Quux,
            list.Contains(item));
    Console.WriteLine("-");
}

输出:

AAA Foo ... [Original object? True]
AAA Foo ... [Original object? True]
AAA Foo ... [Original object? True]
-
BBB Foo ... [Original object? True]
BBB Foo ... [Original object? True]
-
AAA Bar ... [Original object? True]
-
CCC Foo ... [Original object? True]
-
DDD Foo ... [Original object? True]
-
DDD Bar ... [Original object? True]
-
EEE Foo ... [Original object? True]

事实上,IGrouping元素是原始元素。从那里我们可以毫无问题地创建一个新列表。

更新:我在查询外部平展结果集主要是为了能够将组分隔符写入控制台以进行演示,但请参阅 Tim 和 usr 的答案,使用 SelectMany(和等效的 Linq 语法)在查询内部平展。

不用说,这是另一个经典案例,"我们太着急了,让我们在这里和那里阅读一些例子,就是这样",正确的方法是花一点时间学习基础知识。 Linq 不是 SQL。

对于您可能浪费在试图消除混乱上的时间,我深表歉意。 希望其他着急的人现在可能会从这些错误中受益。