如何使用LINQ

时间:2017-10-25 20:41:25

标签: c# list linq

我希望在列表中检索属性值总和的列表,该列表本身是另一个列表的属性,使用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; }
}

我想要检索的是Quantity对于每个PriceTradingDate的{​​{1}}与HourOfDay的总和ProductName

我还没有想出一个有效的解决方案,但作为一个开始,我尝试使用包含所有商品的List<Offer> offers来检索商品价格<10美元的数量:

List<double> quantities = offers.SelectMany(o => o.Bands).Where(b => b.Price < 10).Select(b => b.Quantity)

但我不知道如何GroupBy TradingDateHourOfDay并检索Quantity的总和。对于不同的产品,可以有多个Offer个多个OfferBand s,以及商品Price的各种组合,我只想为所有产品获得Quantity的总和按日期和时间分组的特定价格。

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

修改

我忘了提到的是,QuantityPrice指定TradingDate处没有HourOfDay我想要检索{{1} (或double.NaN)。

示例数据0包含六个List<Offer> offers s:

Offer

选择按日期和时间分组的给定价格的数量总和,将得出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 ------------------------------------------------------------------- 输出:

价格&gt; = 5

List<double>

凡价格= 2

{ 24, 24, 22 }

凡价格= 3

{ 6, 1, double.NaN }

...其中输出是01/01/2017小时1,01/01/2017小时2和02/01/2017小时1的指定价格的所有产品的数量总和。

希望很明显可以遵循。

3 个答案:

答案 0 :(得分:0)

我相信我已经能够管理你所追求的分组了,虽然我没有完成(数量)*(无论价格符合某些条件)的总和,希望这是你可以自定义的东西你需要。

为了让事情分组,我不得不使用几个嵌套的投影并单独进行每个分组(实际上这很有趣,最重要的一点是LINQ的IGrouping并不像你想象的那样直接使用,所以每次我分组时我都会选择一个投影:

var projected = offers.GroupBy(x => x.ProductName)
                                  .Select(x => new
                                  {
                                      ProductName = x.Key,
                                      Dates = x.GroupBy(y => y.TradingDate).ToList()
                                                            .Select(y => new
                                                            {
                                                                TradingDate = y.Key,
                                                                Times = y.GroupBy(z => z.HourOfDay).ToList()
                                                                                      .Select(zx => new
                                                                                      {
                                                                                          Time = zx.Key,
                                                                                          Items = zx.ToList()
                                                                                      })
                                                            })
                                  }).ToList();

希望这会让你足够开始用0项目所需的额外支票,价格不够高等等来进行总结。

请注意,如果您直接使用数据库,此查询可能不是最有效的 - 它可能会提供比此时实际需要的更多信息。但是,我不知道你正在努力开始优化它。

答案 1 :(得分:0)

        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);
        }

答案 2 :(得分:0)

首先,您需要进行过滤,以获得匹配的Offer所需的OfferBands

如果你想把它变成一个函数,你可以创建/传入一个过滤器,我只是将它定义为内联:

Func<Band, bool> filter = (Band b) => b.Price == 3;

由于您不关心ProductName,我使用的是匿名类型,但您可以使用Offer代替var filteredOffers = offers.Select(o => new { TradingDate = o.TradingDate, HourOfDay = o.HourOfDay, OfferBands = o.OfferBands.Where(filter).ToList() }).Where(gb => gb.OfferBands.Count > 0); 。此时,我们也抛出空槽:

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); 空插槽,请对过滤后的数据进行分组并创建字典:

offers

然后,返回原始TradingDate群组,找到所有不同的广告位(HourOfDday + QuantitySum)并将其匹配到double.NaN,填充空广告位List并转换为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();

filteredOffers

重新思考之后,我意识到你可以通过保留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)); 中空的插槽然后在分组后设置它们来简化:

IGrouping

通过使用Key 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)); 返回属性并使用简单的查询形式:

{{1}}