如何使用clojure拉链获取到只有叶子的树中所有子节点的路径?

时间:2019-05-07 21:09:32

标签: clojure tree zipper

说我有一棵这样的树。我想获取到仅包含叶子而不包含非叶子子节点的子节点的路径。

所以对于这棵树

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"]])

edit:我的数据实际上是以路径列表的形式出现的,我正在将其转换为树。但这也许是一种过度并发症吗?

[["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"]]

3 个答案:

答案 0 :(得分:2)

打ic式结构是一个不错的参观场所,但我不想住在那儿。也就是说,它们的编写非常简洁,但是以编程方式操作却非常痛苦,因为语义嵌套结构没有反映在节点的物理结构中。因此,我要做的第一件事就是将其转换为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循环中。困难的部分只是弄清楚基本情况需要什么,以及正确的地图/ mapcat序列以结​​合结果,而不会引入不希望的嵌套级别。

如果您坚持使用打ic,则可以在使用现场消除打de,而不会太过痛苦:

(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样式的树作为内部数据表示形式。

答案 1 :(得分:2)

您绝对可以使用文件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 :(得分:0)

这是因为拉链有很多限制,所以我创建了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"}]]]))))

此后,有很多选择,取决于处理链中的后续步骤。