使用LINQ计算层级数据的总和?

4

能否使用.NET的LINQ对分层数据进行求和?

我的数据类如下:

class Node
{
    public decimal Amount;
    public IEnumerable<Node> Children { get; set; }
}

我有一些数据看起来像这样,但树的深度当然可以任意。

var amounts = new Node
{
    Amount = 10;
    Children = new[]
    {
        new Node
        {
            Amount = 20
        },
        new Node
        {
            Amount = 30
        }
    }
};

有没有一种简单的LINQ查询方式,可以将所有金额加起来并得到结果60?

2个回答

15

你可以使用高阶函数来实现:

Func<Node, decimal> summer = null;
summer = node => node.Amount + 
                 (node.Children == null ? 0m : node.Children.Sum(summer));
decimal total = summer(amounts);

请注意,如果您可以确保node.Children永远不会为null,那么代码可以更简洁:

summer = node => node.Amount + node.Children.Sum(summer);

或者,您可以使用 null 合并运算符:

summer = node => node.Amount + 
                 (node.Children ?? Enumerable.Empty<Node>()).Sum(summer);

当然你可以把这段代码放到一个单独的方法中:

static decimal SumNodes(Node node)
{
    return node.Amount + 
        (node.Children ?? Enumerable.Empty<Node>())
            .Sum((Func<Node, decimal>)SumNodes);
}

需要注意的是这里的丑陋是由于方法组转换中的歧义所致。在类型推断中,方法组并不受到太多关注。

然后调用SumNodes(amount)。有很多选项 :)

第一种形式的完整示例:

using System;
using System.Collections.Generic;
using System.Linq;

class Node
{
    public decimal Amount;
    public IEnumerable<Node> Children { get; set; }
}

public class Test
{
    static void Main()
    {
        var amounts = new Node {
            Amount = 10, Children = new[] {
                new Node { Amount = 20 },
                new Node { Amount = 30 }
            }
        };

        Func<Node, decimal> summer = null;
        summer = node => node.Amount + 
            (node.Children == null ? 0m : node.Children.Sum(summer));

        decimal total = summer(amounts);

        Console.WriteLine(total);
    }
}

我不确定我会称这些为“简单”的LINQ查询...


严格来说,这更像是一个“高阶函数”,而不是真正的“递归lambda”... - Marc Gravell
当我尝试实现SumNodes函数时,出现了“模棱两可的调用”错误。 - Jan Aagaard
啊,是的,方法组调用的怪异问题。我会修复的。 - Jon Skeet
最初,我将其编写为外部方法,并获得了相同的方法组歧义 - 因此,我使用委托=null技巧进行了重构(与您的第一个方法相同)。 - Marc Gravell

2

从技术上讲,你可以编写递归Lambda表达式,但你需要疯狂或极其聪明才能尝试(我还没有弄清楚哪一个)。不过你可以作弊:

    Func<Node, decimal> nodeSum = null;
    nodeSum = node => {
        decimal result = node.Amount;
        if (node.Children != null) {
            result = result + node.Children.Sum(nodeSum);
        }
        return result;
    };
    var value = nodeSum(amounts);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接