C# 如何使用LINQ从列表中的列表中获取分组值

C# 如何使用LINQ从列表中的列表中获取分组值,c#,list,linq,C#,List,Linq,我希望使用LINQ检索一个列表中属性值总和的列表,该列表本身是另一个列表的属性,按父列表中的属性分组 为了解释,我有一份市场报价清单,上面有一系列产品的交易日期和交易时间,以及每个报价中的价格和数量区间清单。我的课程是: public class Offer { public DateTime TradingDate { get; set; } public int HourOfDay { get; set; } public string ProductName { g

我希望使用LINQ检索一个列表中属性值总和的列表,该列表本身是另一个列表的属性,按父列表中的属性分组

为了解释,我有一份市场报价清单,上面有一系列产品的交易日期和交易时间,以及每个报价中的价格和数量区间清单。我的课程是:

public class Offer
{
    public DateTime TradingDate { get; set; }
    public int HourOfDay { get; set; }
    public string ProductName { get; set; }
    public List<Band> OfferBands { get; set; }
}

public class Band
{
    public decimal Price { get; set; }
    public double Quantity { get; set; }
}
但是我不知道如何通过
GroupBy
TradingDate和
HourOfDay
检索
数量的总和。对于不同的产品,可以有多个
报盘
s和多个
报盘
s,以及各种组合的报盘
价格s,我只想得到按日期和时间分组的特定价格下所有产品的数量之和

我可以通过编程实现这一点,但我想要一个LINQ解决方案。谢谢你的帮助

编辑:

我忘了提到的是,如果
交易日期
小时数
在指定的
价格下没有
数量
,我想检索
double.NaN
(或
0

示例数据
列表提供
包含六个
提供
s:

TradingDate | HourOfDay | ProductName |       OfferBands
===================================================================
01/01/2017  |     1     | Chocolate   | Price = 2, Quantity = 6
            |           |             | Price = 5, Quantity = 10
-------------------------------------------------------------------
01/01/2017  |     2     | Chocolate   | Price = 3, Quantity = 6
            |           |             | Price = 5, Quantity = 20
-------------------------------------------------------------------
02/01/2017  |     1     | Chocolate   | Price = 3, Quantity = 7
            |           |             | Price = 6, Quantity = 9
-------------------------------------------------------------------
01/01/2017  |     1     | Cake        | Price = 5, Quantity = 11
            |           |             | Price = 8, Quantity = 3
-------------------------------------------------------------------
01/01/2017  |     2     | Cake        | Price = 2, Quantity = 1
            |           |             | Price = 8, Quantity = 4
-------------------------------------------------------------------
02/01/2017  |     1     | Cake        | Price = 3, Quantity = 9
            |           |             | Price = 5, Quantity = 13
-------------------------------------------------------------------
选择给定价格的数量总和,按日期和时间分组,将给出
列表
输出:

其中价格>=5

{ 24, 24, 22 }
其中价格=2

{ 6, 1, double.NaN }
其中价格=3

{ double.NaN, 6, 16 }
…其中,输出是2017年1月1日第1小时、2017年1月1日第2小时和2017年1月2日第1小时的所有产品在规定价格下的数量总和


希望这一点很清楚。

我相信我已经能够管理您想要的分组,尽管我还没有对(数量)*(无论价格是否符合某些条件)进行求和,因为希望这是您可以定制的东西,但您需要

为了将事情分组,我必须使用几个嵌套的投影,并单独进行每个分组(解决这个问题实际上很有趣,最大的症结在于LINQ的iGroup并不像您预期的那样简单,所以每次分组时,我都使用Select进行投影):

希望,这将给你足够的时间开始做你的总和,无论你需要什么额外的检查0项,价格不够高,等等

请注意,如果您直接使用数据库,此查询可能不是最有效的—它可能会提取比此时实际需要的更多的信息。不过,我对您正在进行的工作了解不够,无法开始对其进行优化。

var offers=new List();
        var offers = new List<Offer>();

        // flatten the nested list linq-style
        var flat = from x in offers
            from y in x.OfferBands
            select new {x.TradingDate, x.HourOfDay, x.ProductName, y.Price, y.Quantity};
        var grouped = from x in flat
            group x by new {x.TradingDate, x.HourOfDay, x.ProductName}
            into g
            select new
            {
                g.Key.TradingDate,
                g.Key.HourOfDay,
                g.Key.ProductName,
                OfferBands = (from y in g
                    group y by new {y.Price}
                    into q
                    select new {Price = q.Key, Quantity = q.Sum(_ => _.Quantity)}).ToList()
            };
        foreach (var item in grouped)
        {
            Console.WriteLine(
                    "TradingDate = {0}, HourOfDay = {1}, ProductName = {2}",
                    item.TradingDate,
                    item.HourOfDay,
                    item.ProductName);
            foreach (var offer in item.OfferBands)
                Console.WriteLine("    Price = {0}, Qty = {1}", offer.Price, offer.Quantity);
        }
//展平嵌套列表linq样式 var flat=从x开始,在报价中 从y到x 选择新{x.TradingDate,x.HourOfDay,x.ProductName,y.Price,y.Quantity}; var分组=从平面中的x开始 按新{x.TradingDate,x.HourOfDay,x.ProductName}划分的x组 进入g 选择新的 { g、 Key.TradingDate, g、 基恩·霍洛夫日, g、 Key.ProductName, OfferBands=(从y到g) 按新{y.Price}划分的y组 进入q 选择新的{Price=q.Key,Quantity=q.Sum({u=>{uq.Quantity)}) }; foreach(分组中的var项目) { 控制台写入线( “TradingDate={0},HourOfDay={1},ProductName={2}”, item.TradingDate, 项目1.1天, 项目名称); foreach(项目中的var报价。报价单) WriteLine(“Price={0},Qty={1}”,offer.Price,offer.Quantity); }
首先,您需要过滤以获得所需的
报价
s和匹配的
报价

您可以创建/传入一个过滤器,如果您想使其成为一个函数,我将以内联方式定义它:

Func<Band, bool> filter = (Band b) => b.Price == 3;
现在,由于您希望为原始数据中已过滤掉的
TradingDate
+
HourOfDay
包含空槽,请将过滤后的数据分组并创建字典:

var mapQuantity = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
                                .Select(og => new { og.Key.TradingDate, og.Key.HourOfDay, QuantitySum = og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) })
                                .ToDictionary(og => new { og.TradingDate, og.HourOfDay }, og => og.QuantitySum);
然后,返回原始的
报价
组,找到所有不同的时段(
TradingDate
+
HourOfDday
),并将它们与
QuantitySum
匹配,用
double.NaN
填充空时段,并转换为
列表

var ans = offers.Select(o => new { o.TradingDate, o.HourOfDay }).Distinct().OrderBy(g => g.TradingDate).ThenBy(g => g.HourOfDay).Select(g => mapQuantity.TryGetValue(g, out var sumq) ? sumq : double.NaN).ToList();
经过重新思考,我意识到可以通过保留
过滤器过滤器中的空插槽来简化,然后在分组后设置其值:

var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() });

var ans = filteredOffers.GroupBy(o => new { o.TradingDate, o.HourOfDay })
                        .OrderBy(og => og.Key.TradingDate).ThenBy(og => og.Key.HourOfDay)
                        .Select(og => (og.Sum(o => o.OfferBands.Count) > 0 ? og.Sum(o => o.OfferBands.Sum(ob => ob.Quantity)) : double.NaN));
通过使用
i分组
记住插槽,您可以简化查询:

var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => {
                    var filteredOBs = obg.SelectMany(ob => ob).Where(filter).ToList();
                    return filteredOBs.Count > 0 ? filteredOBs.Sum(b => b.Quantity) : double.NaN;
                });
如果您愿意放弃
double.NaN
而改为零,您可以让这更简单:

var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => obg.SelectMany(ob => ob).Where(filter).Sum(b => b.Quantity));
最后,为了完成这项任务,一些特殊的扩展方法可以保留
NaN
返回属性,并使用简单的查询表单:

public static class Ext {
    static double ValuePreservingAdd(double a, double b)  => double.IsNaN(a) ? b : double.IsNaN(b) ? a : a + b;
    public static double ValuePreservingSum(this IEnumerable<double> src) => src.Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
    public static double ValuePreservingSum<T>(this IEnumerable<T> src, Func<T, double> select) => src.Select(s => select(s)).Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
}

var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => obg.SelectMany(ob => ob).Where(filter).ValuePreservingSum(b => b.Quantity));
公共静态类Ext{
静态双值保存ADD(双a,双b)=>double.IsNaN(a)→b:double.IsNaN(b)→a:a+b;
公共静态双值保存sum(此IEnumerable src)=>src.聚合(double.NaN,(a,b)=>ValuePreservingAdd(a,b));
public static double ValuePreservingSum(此IEnumerable src,Func select)=>src.select(s=>select(s)).Aggregate(double.NaN,(a,b)=>ValuePreservingAdd(a,b));
}
var ans=offers.GroupBy(o=>new{o.TradingDate,o.HourOfDay},o=>o.OfferBands)
.OrderBy(obg=>obg.Key.TradingDate)。然后by(obg=>obg.Key.HourOfDay)
.Select(obg=>obg.SelectMany(ob=>ob).Where(filter.ValuePreservingSum(b=>b.Quantity));

出于好奇,
HourOfDay
是否与
TradingDate.Hour
不同?如果不是,为什么要将小时存储在多个地方?看来我
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => {
                    var filteredOBs = obg.SelectMany(ob => ob).Where(filter).ToList();
                    return filteredOBs.Count > 0 ? filteredOBs.Sum(b => b.Quantity) : double.NaN;
                });
var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => obg.SelectMany(ob => ob).Where(filter).Sum(b => b.Quantity));
public static class Ext {
    static double ValuePreservingAdd(double a, double b)  => double.IsNaN(a) ? b : double.IsNaN(b) ? a : a + b;
    public static double ValuePreservingSum(this IEnumerable<double> src) => src.Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
    public static double ValuePreservingSum<T>(this IEnumerable<T> src, Func<T, double> select) => src.Select(s => select(s)).Aggregate(double.NaN, (a, b) => ValuePreservingAdd(a, b));
}

var ans = offers.GroupBy(o => new { o.TradingDate, o.HourOfDay }, o => o.OfferBands)
                .OrderBy(obg => obg.Key.TradingDate).ThenBy(obg => obg.Key.HourOfDay)
                .Select(obg => obg.SelectMany(ob => ob).Where(filter).ValuePreservingSum(b => b.Quantity));