Структуры в стиле Иккинг - хорошее место для посещения, но я бы не хотел там жить.То есть они очень лаконичны в написании, но гигантская боль манипулировать программно, потому что семантическая структура вложенности не отражается на физической структуре узлов.Итак, первое, что я хотел бы сделать, это преобразовать в представление дерева в стиле 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 для внутреннего представления данных.