Как получить пути ко всем дочерним узлам в дереве, которые имеют только листья, используя молнии clojure? - PullRequest
1 голос
/ 08 мая 2019

Скажи, что у меня есть такое дерево. Я хотел бы получить пути к дочерним узлам, которые содержат только листья, но не дочерние узлы.

Так что для этого дерева

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 ]

2 голосов
/ 08 мая 2019

Структуры в стиле Иккинг - хорошее место для посещения, но я бы не хотел там жить.То есть они очень лаконичны в написании, но гигантская боль манипулировать программно, потому что семантическая структура вложенности не отражается на физической структуре узлов.Итак, первое, что я хотел бы сделать, это преобразовать в представление дерева в стиле 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 (например, на аналогичный вопрос, на который я недавно ответил в Code Review ).Оказывается, что огромное количество функций на деревьях просто: вызывайте себя рекурсивно для каждого потомка и каким-то образом объединяйте результаты, обычно в возможно вложенном цикле for.Сложная часть - это просто выяснить, каким должен быть ваш базовый вариант, и правильную последовательность карт / картокатов, чтобы объединить результаты без введения нежелательных уровней вложенности.

Если вы настаиваете на том, чтобы придерживаться Hiccup, вы можетеРазберите его на месте использования без особой боли:

(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 для внутреннего представления данных.

2 голосов
/ 08 мая 2019

Вы можете определенно использовать файл 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))))
0 голосов
/ 08 мая 2019

Это потому, что у застежек-молний столько ограничений, что я создал библиотеку Tupelo Forest для обработки древовидных структур данных. Тогда у вашей проблемы есть простое решение:

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

Есть много вариантов после этого в зависимости от следующих шагов в цепочке обработки.

...