首页 > 解决方案 > LINQ sum with grouping,还可以在分组前获取行项值

问题描述

我有一个类型的汽车列表,如下所示List<Car> cars

ID 颜色 制作 模型 公里
1 红色的 宝马 M3 1000
2 红色的 宝马 318i 1000
3 黑色的 梅赛德斯 C200 1000
4 黑色的 梅赛德斯 E200 1000
5 白色的 梅赛德斯 CLA200 1000
6 白色的 梅赛德斯 E200 1000
7 白色的 梅赛德斯 E200 2000
8 白色的 梅赛德斯 E200 3000

现在我想要做的是,使用 LINQ(或者如果不能使用 LINQ,则可能是另一种方式)我想按颜色、品牌、型号对它们进行分组,然后添加另一列并分配给另一种类型的列表(<CarDetail>),这将给出组中线的加权平均值。

例如,在最后 3 辆汽车上,新列将显示(该组的总公里数 = 1000+2000+3000 = 6000)。

6 => 1000/6000
7 => 2000/6000
8 => 3000/6000

到目前为止我尝试过的是:

List<CarDetail> carDetails =
                cars.
                GroupBy(x => new
                {
                    x.Colour,
                    x.Make,
                    x.Model
                }).
            Select(h => new CarDetail
            {
                Colour= h.Key.Colour,
                Make= h.Key.Make,
                Model= h.Key.Model,                    
                WeightedKm = x.Km  / h.Sum(x => x.Km )
            }).ToList();

h.Sum(x => x.Km) 将带来 Km.s 的总和,但尝试使用 "x.Km" 肯定行不通。

任何人都可以帮助如何获取此列表吗?

标签: c#linq

解决方案


分组在概念上是列表的列表。似乎您想将其解压缩为一个简单的列表,沿途使用子列表的聚合。将列表列表转换为列表的过程是 SelectMany

var carDetails = cars
        .GroupBy(c => new
        {
            c.Colour,
            c.Make,
            c.Model
        })
        .SelectMany(g => g, (subList, aCarInTheSublist) => new CarDetail
        {
            Colour= aCarInTheSublist.Colour,
            Make= aCarInTheSublist.Make,
            Model= aCarInTheSublist.Model,                    
            WeightedKm = (double)aCarInTheSublist.Km  / subList.Sum(aCar => aCar.Km ) //cast to double because int math 1000/3000 = 0
        }).ToList();

用 Select 攻击它会遇到麻烦,因为您选择的是组,即外部列表。以这个更简单的例子为例:

A, 1
A, 2
A, 3
B, 1

如果按字母分组,则输出只有 2 行:

A -> {A,1} {A,2} {A,3}
B -> {B,1}

所以你很难回到你想要的 4 行,因为 Select 只运行了两次。

SelectMany 将访问组中的每个条目(即 A 组和 B 两个组,A 组有 3 个子条目)并访问每个子条目,因此对于 A 组(具有 3 个成员的单个组,A1, A2 和 A3) 它将导致 3 个元素向外突出。至关重要的是,虽然它访问了 A1、A2 和 A3 中的每一个,但仍然可以访问整个组 A,因此您可以将其中的所有 KM 相加

它可能效率不高,因为我们反复总结小组,但 LINQ 并不总能赢得效率竞赛 :)

您可以考虑通过将总和放入字典中来用 CPU 时间来换取内存以记住总和:

    var d = cars
        .GroupBy(c => new
        {
            c.Colour,
            c.Make,
            c.Model
        })
        .ToDictionary(g=>g.Key, g=>g.Sum(c=>c.Km));

    var result = cars
        .Select(c => new CarDetail
        {
            Colour= c.Colour,
            Make= c.Make,
            Model= c.Model,                    
            WeightedKm = (double)c.Km  / d[new{c.Colour,c.Make,c.Model}]
        }).ToList();

旁注,当使用 LINQ 时,请尝试保留代表列表条目的别名。cars是汽车的列表,所以Select(c =>为了帮助保持直线,它是一辆车。

AGroupBy(c=>...).Select(g=>提醒您正在对汽车进行分组,然后 Select 正在对分组进行操作。分组中的每个项目都是汽车,因此您可能喜欢Select(gc=>“汽车组”,而Select(gc=> gc.Select(c=>第二个选择是对汽车的可枚举进行操作,因此恢复到c有助于澄清


推荐阅读