【问题标题】:Searching a tree using LINQ使用 LINQ 搜索树
【发布时间】:2011-10-27 03:11:46
【问题描述】:

我有一个从这个类创建的树。

class Node
{
    public string Key { get; }
    public List<Node> Children { get; }
}

我想在所有孩子及其所有孩子中搜索以获取符合条件的孩子:

node.Key == SomeSpecialKey

我该如何实现它?

【问题讨论】:

  • 很有趣,我认为您可以使用 SelectMany 函数完成此操作,记得前段时间必须做类似的事情。

标签: c# .net linq


【解决方案1】:

认为这需要递归是一种误解。它需要一个堆栈或一个队列,最简单的方法是使用递归来实现它。为了完整起见,我将提供一个非递归的答案。

static IEnumerable<Node> Descendants(this Node root)
{
    var nodes = new Stack<Node>(new[] {root});
    while (nodes.Any())
    {
        Node node = nodes.Pop();
        yield return node;
        foreach (var n in node.Children) nodes.Push(n);
    }
}

以这个表达式为例来使用它:

root.Descendants().Where(node => node.Key == SomeSpecialKey)

【讨论】:

  • +1。当树太深以至于递归遍历会破坏调用堆栈并导致StackOverflowException时,此方法将继续工作。
  • @LukeH 虽然在这些情况下有这样的替代方案很有用,但这意味着一棵非常大的树。除非你的树很深,否则递归方法通常更简单/更具可读性。
  • @Tuskan:使用递归迭代器也有性能影响,请参阅blogs.msdn.com/b/wesdyer/archive/2007/03/23/… 的“迭代器成本”部分(诚然,树仍然需要相当深才能引起注意)。而且,首先,我发现 vidstige 的答案与这里的递归答案一样具有可读性。
  • 是的,不要因为性能而选择我的解决方案。除非被证明是瓶颈,否则可读性始终是第一位的。虽然我的解决方案非常简单,所以我想这是一个品味问题......我实际上发布我的答案只是作为递归答案的补充,但我很高兴人们喜欢它。
  • 我认为值得一提的是,上述解决方案执行(最后一个孩子优先)深度优先搜索。如果您想要(第一个孩子优先)广度优先搜索,您可以将节点集合的类型更改为Queue&lt;Node&gt;(相应更改为Enqueue/DequeuePush/Pop )。
【解决方案2】:

如果你想保持类似Linq的语法,你可以使用一个方法来获取所有的后代(孩子+孩子的孩子等)

static class NodeExtensions
{
    public static IEnumerable<Node> Descendants(this Node node)
    {
        return node.Children.Concat(node.Children.SelectMany(n => n.Descendants()));
    }
}

然后可以像任何其他使用 where 或 first 或任何其他方法一样查询此可枚举。

【讨论】:

  • 我喜欢这个,干净! :)
【解决方案3】:

Searching a Tree of Objects with Linq

public static class TreeToEnumerableEx
{
    public static IEnumerable<T> AsDepthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc)
    {
        yield return head;

        foreach (var node in childrenFunc(head))
        {
            foreach (var child in AsDepthFirstEnumerable(node, childrenFunc))
            {
                yield return child;
            }
        }

    }

    public static IEnumerable<T> AsBreadthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc)
    {
        yield return head;

        var last = head;
        foreach (var node in AsBreadthFirstEnumerable(head, childrenFunc))
        {
            foreach (var child in childrenFunc(node))
            {
                yield return child;
                last = child;
            }
            if (last.Equals(node)) yield break;
        }

    }
}

【讨论】:

  • +1 解决了一般问题。链接的文章提供了很好的解释。
  • 要完整,您需要对参数headchildrenFunc 进行空值检查,将方法分成两部分,这样参数检查就不会延迟到遍历时间。
【解决方案4】:

您可以尝试这种扩展方法来枚举树节点:

static IEnumerable<Node> GetTreeNodes(this Node rootNode)
{
    yield return rootNode;
    foreach (var childNode in rootNode.Children)
    {
        foreach (var child in childNode.GetTreeNodes())
            yield return child;
    }
}

然后将其与Where() 子句一起使用:

var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);

【讨论】:

  • 请注意,如果树很深,这种技术效率低下,如果树很深,则会抛出异常。
  • @Eric 好点。欢迎假期回来? (很难说这个遍布全球的互联网事物是什么。)
【解决方案5】:

为什么不使用IEnumerable&lt;T&gt; 扩展方法

public static IEnumerable<TResult> SelectHierarchy<TResult>(this IEnumerable<TResult> source, Func<TResult, IEnumerable<TResult>> collectionSelector, Func<TResult, bool> predicate)
{
    if (source == null)
    {
        yield break;
    }
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
        var childResults = SelectHierarchy(collectionSelector(item), collectionSelector, predicate);
        foreach (var childItem in childResults)
        {
            yield return childItem;
        }
    }
}

那么就这样做

var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);

【讨论】:

    【解决方案6】:

    也许你只需要

    node.Children.Where(child => child.Key == SomeSpecialKey)
    

    或者,如果您需要更深入地搜索,

    node.Children.SelectMany(
            child => child.Children.Where(child => child.Key == SomeSpecialKey))
    

    如果您需要在所有级别进行搜索,请执行以下操作:

    IEnumerable<Node> FlattenAndFilter(Node source)
    {
        List<Node> l = new List();
        if (source.Key == SomeSpecialKey)
            l.Add(source);
        return
            l.Concat(source.Children.SelectMany(child => FlattenAndFilter(child)));
    }
    

    【讨论】:

    • 那会搜索孩子的孩子吗?
    • 我认为这行不通,因为它只搜索树中的一个级别并且不会进行完整的树遍历
    • @Ufuk:第一行只有 1 级深度,第二行只有 2 级深度。如果您需要搜索所有级别,则需要递归函数。
    【解决方案7】:
    public class Node
        {
            string key;
            List<Node> children;
    
            public Node(string key)
            {
                this.key = key;
                children = new List<Node>();
            }
    
            public string Key { get { return key; } }
            public List<Node> Children { get { return children; } }
    
            public Node Find(Func<Node, bool> myFunc)
            {
                foreach (Node node in Children)
                {
                    if (myFunc(node))
                    {
                        return node;
                    }
                    else 
                    {
                        Node test = node.Find(myFunc);
                        if (test != null)
                            return test;
                    }
                }
    
                return null;
            }
        }
    

    然后你可以像这样搜索:

        Node root = new Node("root");
        Node child1 = new Node("child1");
        Node child2 = new Node("child2");
        Node child3 = new Node("child3");
        Node child4 = new Node("child4");
        Node child5 = new Node("child5");
        Node child6 = new Node("child6");
        root.Children.Add(child1);
        root.Children.Add(child2);
        child1.Children.Add(child3);
        child2.Children.Add(child4);
        child4.Children.Add(child5);
        child5.Children.Add(child6);
    
        Node test = root.Find(p => p.Key == "child6");
    

    【讨论】:

    • 因为 Find 的输入是 Func myFunc 您可以使用此方法来过滤您可能在 Node 中定义的任何其他属性。例如,Node 有一个 Name 属性,而您想按名称查找 Node,您可以传入 p => p.Name == "Something"
    【解决方案8】:

    不久前我写了一篇代码项目文章,描述了如何使用 Linq 查询树状结构:

    http://www.codeproject.com/KB/linq/LinqToTree.aspx

    这提供了一个 linq-to-XML 风格的 API,您可以在其中搜索后代、子代、祖先等...

    对于您当前的问题可能有点矫枉过正,但其他人可能会感兴趣。

    【讨论】:

      【解决方案9】:

      您可以使用此扩展方法来查询树。

          public static IEnumerable<Node> InTree(this Node treeNode)
          {
              yield return treeNode;
      
              foreach (var childNode in treeNode.Children)
                  foreach (var flattendChild in InTree(childNode))
                      yield return flattendChild;
          }
      

      【讨论】:

        【解决方案10】:

        我有一个通用扩展方法,可以展平任何IEnumerable&lt;T&gt;,并且从展平的集合中,您可以获得所需的节点。

        public static IEnumerable<T> FlattenHierarchy<T>(this T node, Func<T, IEnumerable<T>> getChildEnumerator)
        {
            yield return node;
            if (getChildEnumerator(node) != null)
            {
                foreach (var child in getChildEnumerator(node))
                {
                    foreach (var childOrDescendant in child.FlattenHierarchy(getChildEnumerator))
                    {
                        yield return childOrDescendant;
                    }
                }
            }
        }
        

        这样使用:

        var q = from node in myTree.FlattenHierarchy(x => x.Children)
                where node.Key == "MyKey"
                select node;
        var theNode = q.SingleOrDefault();
        

        【讨论】:

          【解决方案11】:

          我使用以下实现来枚举树项

              public static IEnumerable<Node> DepthFirstUnfold(this Node root) =>
                  ObjectAsEnumerable(root).Concat(root.Children.SelectMany(DepthFirstUnfold));
          
              public static IEnumerable<Node> BreadthFirstUnfold(this Node root) {
                  var queue = new Queue<IEnumerable<Node>>();
                  queue.Enqueue(ObjectAsEnumerable(root));
          
                  while (queue.Count != 0)
                      foreach (var node in queue.Dequeue()) {
                          yield return node;
                          queue.Enqueue(node.Children);
                      }
              }
          
              private static IEnumerable<T> ObjectAsEnumerable<T>(T obj) {
                  yield return obj;
              }
          

          BreadthFirstUnfold 在上面的实现中使用节点序列队列而不是节点队列。这不是经典的 BFS 算法方式。

          【讨论】:

            【解决方案12】:

            只是为了好玩(大约十年后),一个答案也使用泛型,但带有堆栈和 While 循环,基于@vidstige 接受的答案。

            public static class TypeExtentions
            {
            
                public static IEnumerable<T> Descendants<T>(this T root, Func<T, IEnumerable<T>> selector)
                {
                    var nodes = new Stack<T>(new[] { root });
                    while (nodes.Any())
                    {
                        T node = nodes.Pop();
                        yield return node;
                        foreach (var n in selector(node)) nodes.Push(n);
                    }
                }
            
                public static IEnumerable<T> Descendants<T>(this IEnumerable<T> encounter, Func<T, IEnumerable<T>> selector)
                {
                    var nodes = new Stack<T>(encounter);
                    while (nodes.Any())
                    {
                        T node = nodes.Pop();
                        yield return node;
                        if (selector(node) != null)
                            foreach (var n in selector(node))
                                nodes.Push(n);
                    }
                }
            }
            

            给定一个可以像这样使用的集合

                    var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
            

            或使用根对象

                    var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
            

            【讨论】:

              猜你喜欢
              • 2011-02-27
              • 2013-05-24
              • 1970-01-01
              • 2022-01-24
              • 2015-03-11
              • 2013-10-22
              • 1970-01-01
              • 2013-01-27
              • 1970-01-01
              相关资源
              最近更新 更多