从树结构返回特定节点的功能

时间:2015-08-15 13:33:06

标签: c# linq recursion tree

我正在编写将从树结构返回特定节点的函数。但是当我使用LINQ在树中搜索时,它会在第一个分支中搜索,最后当它到达叶子时,它会抛出空引用异常,因为叶子没有任何孩子。

这是我的班级,

public class Node
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Content { get; set; }
        public IEnumerable<Node> Children { get; set; }
        public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
        {
            return new[] { this }
                   .Concat(Children.SelectMany(child => child.GetNodeAndDescendants()));
        }
    }

这就是我调用此函数的方式,

 var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Name.Contains("string to search"));

OR

var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Id==123)

这样做的正确方法是什么?任何示例代码都会感激不尽

2 个答案:

答案 0 :(得分:0)

如果你不介意依赖第三方解决方案,我有一个我一直在研究的轻量级库,它可以用任何树来完成这个和许多其他事情。它被称为Treenumerable。你可以在GitHub上找到它:https://github.com/jasonmcboyd/Treenumerable;和NuGet上的最新版本(此时为1.2.0):http://www.nuget.org/packages/Treenumerable。它具有良好的测试覆盖率,似乎很稳定。

它确实需要您创建一个帮助器类,该类使用两种方法实现ITreeWalker接口:TryGetParentGetChildren。正如您可能猜到的那样TryGetParent获取节点的父节点,因此您的Node类必须以其知道其父节点的方式进行修改。我想你可以在NotSupported中抛出TryGetParent异常,因为任何遍历操作都不需要该方法。无论如何,无论你走哪条路,下面的代码都会做你想要的:

ITreeWaler<Node> walker;
// Don't forget to instantiate 'walker'.

var foundNode =
    walker
    .PreOrderTraversal(Location)
    .FirstOrdefault(node => node.Name.Contains("string to search"));

我的实现和你的实现之间值得一提的是我的实现不依赖于递归。这意味着您不必担心投掷StackOverflowException的深树。

答案 1 :(得分:0)

编写自己的函数没有错,但基于LINQ或递归迭代器的实现不是一个好主意(性能!)。但为什么依赖外部库?很多你不需要的代码,实现接口,修改你的类等。为预订树遍历编写泛型函数并将其用于任何树结构并不困难。这是我参与How to flatten tree via LINQ?的修改版本(没有什么特别的,普通的迭代实现):

public static class TreeHelper
{
    public static IEnumerable<T> PreOrderTraversal<T>(T node, Func<T, IEnumerable<T>> childrenSelector)
    {
        var stack = new Stack<IEnumerator<T>>();
        var e = Enumerable.Repeat(node, 1).GetEnumerator();
        try
        {
            while (true)
            {
                while (e.MoveNext())
                {
                    var item = e.Current;
                    yield return item;
                    var children = childrenSelector(item);
                    if (children == null) continue;
                    stack.Push(e);
                    e = children.GetEnumerator();
                }
                if (stack.Count == 0) break;
                e.Dispose();
                e = stack.Pop();
            }
        }
        finally
        {
            e.Dispose();
            while (stack.Count != 0) stack.Pop().Dispose();
        }
    }
}

并且您在class Node内的功能变为:

public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
{
    return TreeHelper.PreOrderTraversal(this, node => node.Children);
}

其他一切都按照您的方式进行,并且无需任何问题。

编辑:看起来你需要这样的东西:

public interface IContainer
{
    // ...
}

public class CustomerNodeInstance : IContainer
{
    // ...
}

public class ProductNodeInstance : IContainer
{
    // ...
}

public class Node : IContainer
{
    // ...
    public IEnumerable<IContainer> Children { get; set; }
    public IEnumerable<IContainer> GetNodeAndDescendants() // Note that this method is lazy
    {
        return TreeHelper.PreOrderTraversal<IContainer>(this, item => { var node = item as Node; return node != null ? node.Children : null; });
    }
}