从每个节点节点收集值时如何遍历Clojure中的树?

How to traverse a tree in Clojure while collecting the value from each node node?

我想创建一个从二进制树中的每个节点收集值的函数。 在ClojureDocs中,我发现了一些遍历树/图的功能,例如树序,前走和后走。

https://clojuredocs.org/clojure.core/tree-seq

https://clojuredocs.org/clojure.walk/prewalk

https://clojuredocs.org/clojure.walk/postwalk

这些中的任何一个都可以用来累积所遍历的节点的值吗? 作为Clojure newby,我不知道该怎么做。 如果您知道(用Clojure或类似的Lispy语言),请告诉我。 理想情况下,Clojure newby可以理解您的答案;-)

我的二叉树用这样的节点表示:(值left-child right-child)。 例如:

( 2 (7 nil nil) (88 (5 nil nil) nil) )

在此示例中,我希望函数返回(2 7 88 5)。

注意:遍历方法并不重要。 我只想学习一种收集节点值的技术。


好吧,tree-seq将为您提供(深度优先步行的)节点序列。然后,您可以对其进行任何其他转换,包括(map some-value-extractor-fn (tree-seq ...以获得每个节点中的值。您只需要选择一个树表示形式以及该表示形式的适当功能,以便tree-seq可以知道什么是内部节点及其子节点。
例如,将树的定义用作嵌套列表:

嵌套列表树

我们的树可以分支的节点是列表,我们可以使用list?来标识它们的孩子是第一个之后的值,即他们的rest。因此,我们可以仅使用标准函数来定义tree-seq:

1
2
(->> '( 2 (7 nil nil) (88 (5 nil nil) nil) )
     (tree-seq list? rest))

但这有点浪费-每个nil都作为seq的成员出现,我们感兴趣的每个值都出现在其list节点中,并且本身也成为成员,依此类推。我们可以使用filterremove进行清理-例如,我们可以丢弃所有叶子值,并仅获取内部节点:

1
2
3
4
(->> '( 2 (7 nil nil) (88 (5 nil nil) nil) )
     (tree-seq list? rest)
     (filter list?))
;;=> ((2 (7 nil nil) (88 (5 nil nil) nil)) (7 nil nil) (88 (5 nil nil) nil) (5 nil nil))

然后在这些上方map first

1
2
3
4
(->> '( 2 (7 nil nil) (88 (5 nil nil) nil) )
     (tree-seq list? rest)
     (filter list?)
     (map first)) ;;=>(2 7 88 5)

或者,我们可以尝试丢弃树的内部和nil节点,只取具有值的叶子:

1
2
3
(->> '( 2 (7 nil nil) (88 (5 nil nil) nil) )
     (tree-seq list? seq)
     (remove (some-fn list? nil?))) ;;=>(2 7 88 5)

请注意,在此策略中,我必须使用seq而不是rest,因为我希望列表中的第一个值也是该节点的子级。 (some-fn list? nil?)是一些高阶函数-它构建了一个函数来检查输入是否满足谓词list?nil?(或两者)。

嵌套地图树

如果您想要一个更一般的树定义,其中每个节点可以包含多个值以及可变数量的子代,则可以将树定义为嵌套映射:{:value 2 :children [{:value 7} {:value 88 :children [{:value 5}]}]}

在这种情况下,通常仅将地图视为节点是最简单的。我们可能的分支节点是maps-用map?检查。我们将他们的孩子存储在键:children中,该键是一个关键字,也是一个函数。我们使用该功能来获取孩子。

1
2
3
(->> {:value 2 :children [{:value 7} {:value 88 :children [{:value 5}]}]}
     (tree-seq map? :children))
;;=> ({:value 2, :children [{:value 7} {:value 88, :children [{:value 5}]}]} {:value 7} {:value 88, :children [{:value 5}]} {:value 5})

然后只需在节点上map从节点中获取所需的数据:

1
2
3
(->> {:value 2 :children [{:value 7} {:value 88 :children [{:value 5}]}] }
     (tree-seq map? :children)
     (map :value)) ;;=> (2 7 88 5)


为了累积树的节点值,我有一个非功能性的解决方案:

1
2
3
4
5
6
user> (def a (atom 0))
#'user/a
user> (dorun (clojure.walk/postwalk #(when (number? %) (swap! a (partial + %))) '( 2 (7 nil nil) (88 (5 nil nil) nil))))
nil
user> @a
102