如何解析clojure中的异构树

时间:2018-01-28 15:08:06

标签: clojure tree

我正在处理一些Clojure代码,其中我有一个实体树,表示为嵌套向量,如下所示:

(def tree '[SYMB1 "a" [SYMB2 {:k1 [SYMB1 "b" "c"]} "x"] {:k2 ["b" "c"]})

这里,叶子是字符串,节点可以是符号或映射。每张地图都有一个与子树或一组树叶相关联的关键字。

如何渲染上面的树以获得:

[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] "b" "c"]

4 个答案:

答案 0 :(得分:1)

看起来你只想在遇到地图时丢弃:k1:k2(假设每张地图只有1个键)。您可以使用postwalk

轻松完成此操作
(ns ...
  (:require
    [clojure.walk :as walk]
  ))

(def tree
  '[SYMB1 "a" [SYMB2 {k1 [SYMB1 "b" "c"]} "x"] {k2 ["b" "c"]} ])

(def desired
  '[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]])


  (let [result  (walk/postwalk
                  (fn [item]
                    (cond
                      (map? item) (do
                                    (when-not (= 1 (count item))
                                      (throw (ex-info "Must be only 1 item" {:item item})))
                                    (val (first item)))
                      :else item ))
                  tree) ]
    (is= desired result))

result => [SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]]

请注意,:k2的结果仍然包含在矢量中,与原始问题不同。我不确定这是不是你的意思。

答案 1 :(得分:1)

使用clojure.spec:

(ns tree
  (:require [clojure.spec.alpha :as s]))

(def tree '[SYMB1 "a" [SYMB2 {:k1 [SYMB1 "b" "c"]} "x"] {:k2 ["b" "c"]}])

(s/def ::leaf string?)
(s/def ::leafs (s/coll-of ::leaf))

(s/def ::map
  (s/and
   map?
   (s/conformer
    (fn [m]
      (let [[_ v] (first m)]
        (s/conform (s/or
                    :node  ::node
                    :leafs ::leafs) v))))))

(s/def ::node (s/and
               (s/or :symbol ::symbol
                     :leaf ::leaf
                     :map ::map)
               (s/conformer second)))

(s/def ::symbol
  (s/and
   (s/cat :name
          symbol?
          :children
          (s/* ::node))
   (s/conformer (fn [parsed]
                  (let [{:keys [name children]} parsed]
                    (reduce
                     (fn [acc v]
                       (case (first v)
                         :leafs (into acc (second v))
                         :node  (conj acc (second v))
                         (conj acc v)))
                     [name]
                     children))))))

(s/conform ::node tree) ;; [SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] "b" "c"]

答案 2 :(得分:0)

我找到了一个使用postwak和一些辅助函数的解决方案:

(defn clause-coll? [item]
  (and (vector? item)
       (symbol? (first item))))

(defn render-map[amap]
  (let [[[_ v]] (vec amap)]
    (if (clause-coll? v)
      [v]
       v)))

(defn render-item[item]
  (if (map? item) 
   (render-map item)
   [item]))

(defn render-level [[op & etc]]
  (->> (mapcat render-item etc)
       (cons op)))

(defn parse-tree[form]
  (clojure.walk/postwalk #(if (clause-coll? %)
                              (render-level %)
                              %)
                         form))

答案 3 :(得分:0)

Michiel的clojure.spec解决方案很聪明,而Alan的clojure.walk解决方案很简洁。

不使用任何库并直接走树:

(def tree
     '[SYMB1 "a"
             [SYMB2 {:k1 [SYMB1 "b" "c"]}
                    "x"]
                    {:k2 ["b" "c"]}])

(defn get-new-keys
  "Determines next keys vector for tree navigation, can backtrack."
  [source-tree current-keys current-node]
  (if (and (vector? current-node) (symbol? (first current-node)))
      (conj current-keys 0)
      (let [last-index (->> current-keys count dec)]
        (let [forward-keys (update-in current-keys [last-index] inc)
              forward-node (get-in source-tree forward-keys)]
              (if forward-node
                  forward-keys
                  (if (= 1 (count current-keys))
                      current-keys
                      (recur source-tree (subvec current-keys 0 last-index) current-node)))))))

(defn convert-tree
      "Converts nested vector source tree to target tree."
      ([source-tree] (convert-tree source-tree [0] []))
      ([source-tree keys target-tree]
        (let [init-node       (get-in source-tree keys)
              node            (if (map? init-node)
                                  (first (vals init-node))
                                  (if (vector? init-node)
                                      []
                                      init-node))
              new-target-tree (update-in target-tree keys (constantly node))
              new-keys        (get-new-keys source-tree keys init-node)]
      (if (= new-keys keys)
          new-target-tree
          (recur source-tree new-keys new-target-tree)))))

user=> (convert-tree tree)
[SYMB1 "a" [SYMB2 [SYMB1 "b" "c"] "x"] ["b" "c"]]