[英]How to get grouped values from List within List with 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
有一定Price
為每TradingDate
和HourOfDay
,每一個ProductName
。
我還沒有想出一個可行的解決方案,但作為開始,我正在嘗試類似的東西(使用包含所有報價的List<Offer> offers
報價,以檢索報價 < 10 美元的數量):
List<double> quantities = offers.SelectMany(o => o.Bands).Where(b => b.Price < 10).Select(b => b.Quantity)
但我不知道如何對TradingDate
和HourOfDay
進行GroupBy
並檢索Quantity
的總和。 可以有多個Offer
s的多OfferBand
S代表不同的產品,提供的各種組合Price
s,而我只想得到的金額Quantity
在一定的價格按日期和時間進行分組的所有產品。
我可以以編程方式實現這一點,但我想要一個 LINQ 解決方案。 謝謝你的幫助。
編輯:
我忘記提及的是,如果TradingDate
和HourOfDay
的指定Price
沒有Quantity
,我想檢索double.NaN
(或0
)。
使用包含六個Offer
的示例數據List<Offer> offers
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
-------------------------------------------------------------------
選擇給定價格的數量總和,按日期和時間分組,將給出List<double>
輸出:
價格 >= 5
{ 24, 24, 22 }
其中價格 = 2
{ 6, 1, double.NaN }
價格 = 3
{ double.NaN, 6, 16 }
...其中輸出是 01/01/2017 小時 1、01/01/2017 小時 2 和 02/01/2017 小時 1 中所有產品在指定價格下的數量總和。
希望這很清楚。
我相信我已經能夠管理您所追求的分組,盡管我還沒有完成(數量)*(無論價格與某些條件相匹配的任何價格)的總和,因為希望這是您可以自定義的東西,但是您需要.
為了對事物進行分組,我不得不使用多個嵌套投影並單獨進行每個分組(解決這個問題實際上很有趣,最大的症結在於 LINQ 的 IGrouping 不像您預期的那樣易於使用,因此每個我分組的時候我用 Select 做了一個投影):
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 件物品進行任何額外的檢查,價格不夠高,等等。
請注意,如果您直接使用數據庫,此查詢可能不是最有效的 - 它可能會提取比此時實際需要的更多信息。 不過,我對您正在做的工作了解得不夠多,無法開始優化它。
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);
}
首先,您需要使用匹配的OfferBands
進行過濾以獲取所需的Offer
。
如果你想讓它成為一個函數,你可以創建/傳入一個過濾器,我將直接定義它:
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));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.