【问题标题】:Functionally traversing a tree in C#在 C# 中功能性地遍历树
【发布时间】:2009-11-06 01:52:33
【问题描述】:

考虑以下c#中的扩展方法,Traverse:

IEnumerable<T> Traverse<T>( this IEnumerable<T> source, 
                              Func<T, IEnumerable<T>> fnRecurse );

此方法允许人们通过 T 定义的树以及导致 T 返回其子节点的任何函数递归。

现在考虑 T 的以下实现:

class Node
{
  public string Name;
  public List<Node> Children;
}

我的目标是编写返回 IEnumerable 的最短函数 包含此树中每个节点的完全限定路径。比如:

var node = GetParentNode();
return node.Traverse( node => node.Children )
           .Select( node => GetParentName(node) + ":" + node.Name );

显然,将 Parent 属性添加到 Node 会使问题变得微不足道。相反,我想以某种方式在仿函数中构建我的父字符串。我认为这在 C++ 中不会太难,但我不知道如何在 C# 中做到这一点。有什么想法吗?

【问题讨论】:

    标签: c# linq functional-programming


    【解决方案1】:

    我认为诀窍是不传递Node 类型。而是传递Node,它是合格的路径。例如

    var node = GetTheStartNode();
    var start = new { Path = node.Name; Node = node };
    var paths = 
       start
         .Traverse( x => x.Node.Children.Select(
            c => new { .Path = x.Path + ":" c.Name; .Node=c) )
         .Select(x => x.Path);
    

    【讨论】:

    • 我只是输入了完全相同的答案 :) (除非你在 C# 中不需要“With” :)
    • @Tony,很好理解。每天用 4 种语言工作并不适合连贯的 SO 答案:)
    • @Tony,当你切换回 Jon 时,Twitter 风格的 cmets 看起来会非常有趣
    • 是的。我“期待”了一系列元问题,询问为什么托尼也改名了......
    【解决方案2】:

    最清晰和最可重复使用的解决方案:

    创建一个枚举所有可能路径的通用方法:

    static IEnumerable<IEnumerable<T>> ComputePaths<T>(T Root, Func<T, IEnumerable<T>> Children) {
        yield return new[] { Root };
        foreach (var Child in Children(Root)) 
            foreach (var ChildPath in ComputePaths(Child, Children)) 
                yield return new[] { Root }.Concat(ChildPath);            
    }
    

    生成的枚举可以很容易地转换为您的字符串表示形式。

        // All paths
        var Paths = ComputePaths(Test, x => x.Children);
    
        // Compute string representation 
        var StrPaths = from p in Paths select string.Join(":", p.Select(t => t.Name).ToArray());
    
        foreach(var p in StrPaths)
            Console.WriteLine(p);
    

    【讨论】:

      【解决方案3】:

      好吧,我认为在不存储父指针的情况下找到要查找的节点之前,您无法避免搜索树。

      您将从根目录开始。测试当前节点是否匹配。如果匹配,则返回节点或仅返回名称(作为这一元素的列表)。否则,如果这是叶节点,则返回 null。如果不是叶子,则遍历它的子节点,如果子节点返回非空值,则将当前节点添加到列表中并返回。

      从原始调用返回,null 表示未找到匹配项。否则,您将按顺序排列节点列表。

      【讨论】:

        猜你喜欢
        • 2016-05-04
        • 1970-01-01
        • 2012-09-12
        • 1970-01-01
        • 2012-07-09
        • 1970-01-01
        • 2010-12-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多