Linq递归总和

时间:2018-01-25 23:50:17

标签: c# linq recursion

我有以下数据结构:

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

帮助表示赞赏。

4 个答案:

答案 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