【问题标题】:How to obtain paths to all the child nodes in a tree that only have leaves using clojure zippers?如何使用 clojure 拉链获取树中只有叶子的所有子节点的路径?
【发布时间】:2019-09-25 14:00:14
【问题描述】:

假设我有一棵这样的树。我想获取只包含叶子而不包含非叶子子节点的子节点的路径。

所以对于这棵树

root
├──leaf123
├──level_a_node1
│   ├──leaf456
├──level_a_node2
│  ├──level_b_node1
│  │  └──leaf987
│  └──level_b_node2
│     └──level_c_node1
|        └── leaf654
├──leaf789
└──level_a_node3
   └──leaf432

结果是

[["root"  "level_a_node1"]
["root"  "level_a_node2" "level_b_node1"]
["root"  "level_a_node2" "level_b_node2" "level_c_node1"]
["root"  "level_a_node3"]]

我试图深入到底部节点并检查(lefts)(rights) 是否不是分支,但这并不完全有效。

(z/vector-zip ["root"
               ["level_a_node3" ["leaf432"]]
               ["level_a_node2" ["level_b_node2" ["level_c_node1" ["leaf654"]]] ["level_b_node1" ["leaf987"]] ["leaf789"]]
               ["level_a_node1" ["leaf456"]]
               ["leaf123"]])

编辑:我的数据实际上是以路径列表的形式出现的,我正在将其转换为树。但也许这过于复杂了?

[["root" "leaf"]
["root"  "level_a_node1" "leaf"]
["root"  "level_a_node2" "leaf"]
["root"  "level_a_node2" "level_b_node1" "leaf"]
["root"  "level_a_node2" "level_b_node2" "level_c_node1" "leaf"]
["root"  "level_a_node3" "leaf"]]

【问题讨论】:

    标签: clojure tree zipper


    【解决方案1】:

    您绝对可以使用文件 api 来导航目录。如果使用拉链,你可以这样做:

    (loop [loc (vector-zip ["root"
                            ["level_a_node3"
                             ["leaf432"]]
                            ["level_a_node2"
                             ["level_b_node2"
                              ["level_c_node1"
                               ["leaf654"]]]
                             ["level_b_node1"
                              ["leaf987"]]
                             ["leaf789"]]
                            ["level_a_node1"
                             ["leaf456" "leaf456b"]]
                            ["leaf123"]])
           ans nil]
      (if (end? loc)
        ans
        (recur (next loc)
               (cond->> ans
                 (contains-leaves-only? loc)
                 (cons (->> loc down path (map node)))))))
    

    会输出这个:

    (("root" "level_a_node1")
     ("root" "level_a_node2" "level_b_node1")
     ("root" "level_a_node2" "level_b_node2" "level_c_node1")
     ("root" "level_a_node3"))
    

    通过定义树的方式,可以实现辅助函数 如:

    (def is-leaf? #(-> % down nil?))
    
    (defn contains-leaves-only?
      [loc]
      (some->> loc
               down            ;; branch name
               right           ;; children list
               down            ;; first child
               (iterate right) ;; with other sibiling
               (take-while identity)
               (every? is-leaf?)))
    

    更新 - 添加惰性序列版本

    (->> ["root"
          ["level_a_node3"
          ["leaf432"]]
          ["level_a_node2"
          ["level_b_node2"
            ["level_c_node1"
            ["leaf654"]]]
          ["level_b_node1"
            ["leaf987"]]
          ["leaf789"]]
          ["level_a_node1"
          ["leaf456" "leaf456b"]]
          ["leaf123"]]
         vector-zip
         (iterate next)
         (take-while (complement end?))
         (filter contains-leaves-only?)
         (map #(->> % down path (map node))))
    

    【讨论】:

      【解决方案2】:

      正是因为拉链有很多限制,我创建了the Tupelo Forest library 来处理树状数据结构。那么你的问题就有了一个简单的解决方案:

      (ns tst.tupelo.forest-examples
        (:use tupelo.core tupelo.forest tupelo.test))
      
        (with-forest (new-forest)
          (let [data          ["root"
                               ["level_a_node3" ["leaf"]]
                               ["level_a_node2"
                                ["level_b_node2"
                                 ["level_c_node1"
                                  ["leaf"]]]
                                ["level_b_node1" ["leaf"]]]
                               ["level_a_node1" ["leaf"]]
                               ["leaf"]]
                root-hid      (add-tree-hiccup data)
                leaf-paths    (find-paths-with root-hid [:** :*] leaf-path?)]
      

      有一棵看起来像这样的树:

      (hid->bush root-hid) => 
          [{:tag "root"}
           [{:tag "level_a_node3"}
            [{:tag "leaf"}]]
           [{:tag "level_a_node2"}
            [{:tag "level_b_node2"}
             [{:tag "level_c_node1"}
              [{:tag "leaf"}]]]
            [{:tag "level_b_node1"}
             [{:tag "leaf"}]]]
           [{:tag "level_a_node1"}
            [{:tag "leaf"}]]
           [{:tag "leaf"}]])
      

      结果如下:

      (format-paths leaf-paths) => 
          [[{:tag "root"} [{:tag "level_a_node3"} [{:tag "leaf"}]]]
           [{:tag "root"} [{:tag "level_a_node2"} [{:tag "level_b_node2"} [{:tag "level_c_node1"} [{:tag "leaf"}]]]]]
           [{:tag "root"} [{:tag "level_a_node2"} [{:tag "level_b_node1"} [{:tag "leaf"}]]]]
           [{:tag "root"} [{:tag "level_a_node1"} [{:tag "leaf"}]]]
           [{:tag "root"} [{:tag "leaf"}]]]))))
      

      这之后有很多选择,具体取决于处理链中的后续步骤。

      【讨论】:

        【解决方案3】:

        打嗝式建筑是一个不错的参观地点,但我不想住在那里。也就是说,它们写起来非常简洁,但是以编程方式操作却是一个巨大的痛苦,因为语义嵌套结构没有反映在节点的物理结构中。所以,我要做的第一件事就是转换为 Enlive 风格的树表示(或者,理想情况下,首先生成 Enlive):

        (def hiccup
          ["root"
           ["level_a_node3" ["leaf432"]]
           ["level_a_node2"
            ["level_b_node2"
             ["level_c_node1"
              ["leaf654"]]]
            ["level_b_node1"
             ["leaf987"]]
            ["leaf789"]]
           ["level_a_node1"
            ["leaf456"]]
           ["leaf123"]])
        (defn hiccup->enlive [x]
          (when (vector? x)
            {:tag (first x)
             :content (map hiccup->enlive (rest x))}))
        (def enlive (hiccup->enlive hiccup))
        
        ;; Yielding...
        {:tag "root",
         :content
         ({:tag "level_a_node3", :content ({:tag "leaf432", :content ()})}
          {:tag "level_a_node2",
           :content
           ({:tag "level_b_node2",
             :content
             ({:tag "level_c_node1",
               :content ({:tag "leaf654", :content ()})})}
            {:tag "level_b_node1", :content ({:tag "leaf987", :content ()})}
            {:tag "leaf789", :content ()})}
          {:tag "level_a_node1", :content ({:tag "leaf456", :content ()})}
          {:tag "leaf123", :content ()})}
        

        完成此操作后,您最不想使用的就是拉链了。它们是目标遍历的好工具,您非常关心您正在处理的节点附近的结构。但是如果你只关心节点及其子节点,那么编写一个简单的递归函数来遍历树就容易多了:

        (defn paths-to-leaves [{:keys [tag content] :as root}]
          (when (seq content)
            (if (every? #(empty? (:content %)) content)
              [(list tag)]
              (for [child content
                    path (paths-to-leaves child)]
                (cons tag path)))))
        

        像这样编写递归遍历的能力是一项技能,它将在您的 Clojure 职业生涯中多次为您服务(例如,a similar question I recently answered on Code Review)。事实证明,树上的大量函数只是:在每个孩子上递归调用自己,并以某种方式组合结果,通常在一个可能嵌套的 for 循环中。困难的部分只是弄清楚你的基本情况需要什么,以及正确的地图/地图猫序列来组合结果而不引入不想要的嵌套级别。

        如果你坚持坚持使用小嗝嗝,你可以在使用现场解开它,不会太痛苦:

        (defn hiccup-paths-to-leaves [node]
          (when (vector? node)
            (let [tag (first node), content (next node)]
              (if (and content (every? #(= 1 (count %)) content))
                [(list tag)]
                (for [child content
                      path (hiccup-paths-to-leaves child)]
                  (cons tag path))))))
        

        但它明显更混乱,而且每次处理树时都必须重复工作。我再次鼓励您使用 Enlive 风格的树来表示您的内部数据。

        【讨论】:

        • 感谢您的回答。我不喜欢任何树结构,实际上我的数据是以文件路径列表的形式出现的,我正在将其转换为树。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-28
        • 2015-10-01
        • 2017-11-29
        • 1970-01-01
        • 1970-01-01
        • 2019-07-15
        • 1970-01-01
        相关资源
        最近更新 更多