我有以下数据结构:
List<Item> Items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
我想构建一个查询,该查询获取其父项中所有子项价格的总和(项目与Id_Parent相关)。 例如,对于Item Id = 100,我有55,因为这是其子项的值。
对于项目ID = 3我有21,因为项目ID = 5和Id = 9都是该总和。 到目前为止太好了。
我喜欢得到的是Item Id = 1我也应该得到sum = 21,因为Id = 3是Id = 1的孩子,它的总和是21。
这是我的代码:
var result = from i in items
join item in item on i.Id_Parent equals item.Id
select new
{
Name = prod.Nome,
Sum =
(from it in items
where it.Id_Parent == item.Id
group it by new
{
it.Id_Parent
}
into g
select new
{
Sum = g.Sum(x => x.Price)
}
).First()
};
帮助表示赞赏。
答案 0 :(得分:3)
创建递归函数以查找父项的所有子项:
public static IEnumerable<Item> ItemDescendents(IEnumerable<Item> src, int parent_id) {
foreach (var item in src.Where(i => i.Id_Parent == parent_id)) {
yield return item;
foreach (var itemd in ItemDescendents(src, item.Id))
yield return itemd;
}
}
现在您可以获得任何父母的价格:
var price1 = ItemDescendants(Items, 1).Sum(i => i.Price);
请注意,如果您知道项目的子项ID值始终大于其父项,则不需要递归:
var descendents = Items.OrderBy(i => i.Id).Aggregate(new List<Item>(), (ans, i) => {
if (i.Id_Parent == 1 || ans.Select(a => a.Id).Contains(i.Id_Parent))
ans.Add(i);
return ans;
});
对于那些喜欢避免递归的人,可以改为使用显式堆栈:
public static IEnumerable<Item> ItemDescendentsFlat(IEnumerable<Item> src, int parent_id) {
void PushRange<T>(Stack<T> s, IEnumerable<T> Ts) {
foreach (var aT in Ts)
s.Push(aT);
}
var itemStack = new Stack<Item>(src.Where(i => i.Id_Parent == parent_id));
while (itemStack.Count > 0) {
var item = itemStack.Pop();
PushRange(itemStack, src.Where(i => i.Id_Parent == item.Id));
yield return item;
}
}
我包含PushRange
辅助函数,因为Stack
没有辅助函数。
最后,这是一个不使用任何堆栈,隐式或显式的变体。
public IEnumerable<Item> ItemDescendantsFlat2(IEnumerable<Item> src, int parent_id) {
var children = src.Where(s => s.Id_Parent == parent_id);
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => src.Where(i => i.Id_Parent == c.Id)).ToList();
} while (children.Count() > 0);
}
您也可以使用Lookup
替换源的多次遍历:
public IEnumerable<Item> ItemDescendantsFlat3(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var children = childItems[parent_id];
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => childItems[c.Id]).ToList();
} while (children.Count() > 0);
}
我根据有关嵌套枚举过多的注释优化了上述内容,这大大提高了性能,但我也受到启发,尝试删除SelectMany
这可能很慢,并收集IEnumerable
为我见过其他地方建议优化Concat
:
public IEnumerable<Item> ItemDescendantsFlat4(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var stackOfChildren = new Stack<IEnumerable<Item>>();
stackOfChildren.Push(childItems[parent_id]);
do
foreach (var c in stackOfChildren.Pop()) {
yield return c;
stackOfChildren.Push(childItems[c.Id]);
}
while (stackOfChildren.Count > 0);
}
@AntonínLejsek的GetDescendants
仍然是最快的,虽然它现在非常接近,但有时更简单的胜出表现。
答案 1 :(得分:1)
简单的方法是使用本地函数,如下所示:
int CalculatePrice(int id)
{
int price = Items.Where(item => item.Id_Parent == id).Sum(child => CalculatePrice(child.Id));
return price + Items.First(item => item.Id == id).Price;
}
int total = CalculatePrice(3); // 3 is just an example id
另一个更清洁的解决方案是使用Y combinator创建一个可以内联调用的闭包。假设你有这个
/// <summary>
/// Implements a recursive function that takes a single parameter
/// </summary>
/// <typeparam name="T">The Type of the Func parameter</typeparam>
/// <typeparam name="TResult">The Type of the value returned by the recursive function</typeparam>
/// <param name="f">The function that returns the recursive Func to execute</param>
/// <returns>The recursive Func with the given code</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
Func<T, TResult> g = null;
g = f(a => g(a));
return g;
}
然后你就可以得到你的结果:
int total = Y<int, int>(x => y =>
{
int price = Items.Where(item => item.Id_Parent == y).Sum(child => x(child.Id));
return price + Items.First(item => item.Id == y).Price;
})(3);
它的优点在于它允许你以功能方式快速声明和调用递归函数,这在像这样的情况下尤其方便,你只需要&#34;一次性&# 34;您只需使用一次的功能。此外,由于此函数非常小,使用Y组合器进一步减少了必须声明本地函数并在另一行上调用它的样板。
答案 2 :(得分:0)
对于可能遇到StackOverflowException
的未来读者,我使用的替代方案在以下示例中:(dotnetfiddle example)
使用System; 使用System.Collections.Generic; 使用System.Linq;
public class Program
{
public static void Main()
{
var items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
foreach(var item in items)
{
Console.WriteLine("{0} {1} $" + GetSum(items, item.Id).ToString(), item.Name, item.Id);
}
}
public static int GetSum(IEnumerable<Item> items, int id)
{
// add all matching items
var itemsToSum = items.Where(i => i.Id == id).ToList();
var oldCount = 0;
var currentCount = itemsToSum.Count();
// it nothing was added we skip the while
while (currentCount != oldCount)
{
oldCount = currentCount;
// find all matching items except the ones already in the list
var matchedItems = items
.Join(itemsToSum, item => item.Id_Parent, sum => sum.Id, (item, sum) => item)
.Except(itemsToSum)
.ToList();
itemsToSum.AddRange(matchedItems);
currentCount = itemsToSum.Count;
}
return itemsToSum.Sum(i => i.Price);
}
public class Item
{
public int Id { get; set; }
public int Id_Parent { get; set; }
public int Number { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
}
结果:
机器1 $ 21
Machine1 3 $ 21
Machine1-A 5 $ 10
Machine1-B 9 $ 11
项目100 $ 55
Item1 112 $ 55
基本上我们创建一个列表,其中包含与传递的id匹配的初始项。如果id不匹配,我们没有项目,我们跳过while循环。如果我们确实有项目,那么我们加入以查找所有具有我们当前项目的父ID的项目。然后,我们从列表中排除列表中已有的列表。然后追加我们发现的东西。最终,列表中没有更多具有匹配父ID的项目。
答案 3 :(得分:0)
有很多解决方案值得制作基准。我也将我的解决方案添加到混音中,这是最后一个功能。有些函数包括根节点,有些函数不包含,但除此之外,它们返回相同的结果。我测试了宽树,每个父母有2个孩子,每个父母只有一个孩子的狭窄树(深度等于项目数)。结果是:
v=df['MA(5)-MA(20)'].gt(0).astype(int).diff().fillna(0).cumsum()
df.groupby(v).High.transform('max').mask(df['MA(5)-MA(20)'] == 0,df.groupby(v).Low.transform('min'))
0 90
1 90
2 102
3 102
Name: High, dtype: int64
虽然过早优化很糟糕,但了解渐近行为是很重要的。渐近行为确定算法是否会缩放或死亡。
代码如下
---------- Wide 100000 3 ----------
ItemDescendents: 9592ms
ItemDescendentsFlat: 9544ms
ItemDescendentsFlat2: 45826ms
ItemDescendentsFlat3: 30ms
ItemDescendentsFlat4: 11ms
CalculatePrice: 23849ms
Y: 24265ms
GetSum: 62ms
GetDescendants: 19ms
---------- Narrow 3000 3 ----------
ItemDescendents: 100ms
ItemDescendentsFlat: 24ms
ItemDescendentsFlat2: 75948ms
ItemDescendentsFlat3: 1004ms
ItemDescendentsFlat4: 1ms
CalculatePrice: 69ms
Y: 69ms
GetSum: 915ms
GetDescendants: 0ms