从Clojure的嵌套列表中获取平面记录数据结构

时间:2019-09-12 17:38:09

标签: clojure record tree-traversal

比方说,我有一个清单列表,代表Clojure中的树结构,例如

'(a (b (c d)) (e (f)))

,我想将其转换为这样的记录格式(以将其传递给可视化程序包):

[{:id "0" :label "a" :parent nil}
 {:id "1" :label "b" :parent "0"}
 {:id "2" :label "c" :parent "1"}
 {:id "3" :label "d" :parent "1"}
 {:id "4" :label "e" :parent "0"}
 {:id "5" :label "f" :parent "4"}]

解决这个问题的正确方法是什么? 我对此感到很不满意,但我会考虑从defrecord开始,然后再考虑通过树循环的一种方式,但我不知道该如何开始。

(def tree '(a (b (c d)) (e (f))))
(defn list-to-record [l]
  (defrecord rec [id name parent])
  (let [counter (atom 0)]
  (into [] (map ->rec 
                      ... ... ...))))

(list-to-record tree)

也许我应该使用clojure.walk


编辑:澄清一下,无论标签是什么,它都应该起作用,因此更改输入列表中的标签对结果结构(每个:id的:parent值)不起作用。也就是说,下面的列表与上面的一样,但标签彼此相同

'(a (a (a a)) (a (a)))

应该翻译成

[{:id "0" :label "a" :parent nil}
 {:id "1" :label "a" :parent "0"}
 {:id "2" :label "a" :parent "1"}
 {:id "3" :label "a" :parent "1"}
 {:id "4" :label "a" :parent "0"}
 {:id "5" :label "a" :parent "4"}]

2 个答案:

答案 0 :(得分:1)

这是使用Clojure拉链和loop + recur的一种方法:

(defn index-zipper [z]
  (loop [loc z, next-id 0, parent-ids [], acc []]
    (cond
      (z/end? loc) acc

      (and (z/node loc) (not (z/branch? loc)))
      (recur
        (z/next loc)
        (inc next-id)
        (cond
          (some-> (z/right loc) z/branch?) (conj parent-ids next-id)
          (not (z/right loc)) (some-> parent-ids not-empty pop)
          :else parent-ids)
        (conj acc
              {:id     (str next-id)
               :label  (str (z/node loc))
               :parent (when (seq parent-ids)
                         (str (peek parent-ids)))}))

      :else
      (recur (z/next loc) next-id parent-ids acc))))

loop具有以下绑定:

  1. 当前的拉链loc版本
  2. 下一个:id值,每次我们看到叶子节点时都会增加
  3. 当前父:id
  4. 堆栈(向量),当拉链下降/上升时将被推入/弹出。每个叶节点的:parent值位于parent-ids堆栈的顶部。
  5. 叶节点图的
  6. acc累加器向量

您可以使用拉链调用该函数:

(index-zipper (z/seq-zip '(a (b (c d)) (e (f)))))
=>
[{:id "0", :label "a", :parent nil}
 {:id "1", :label "b", :parent "0"}
 {:id "2", :label "c", :parent "1"}
 {:id "3", :label "d", :parent "1"}
 {:id "4", :label "e", :parent "0"}
 {:id "5", :label "f", :parent "4"}]

(index-zipper (z/seq-zip '(a (a (a a)) (a (a)))))
=>
[{:id "0", :label "a", :parent nil}
 {:id "1", :label "a", :parent "0"}
 {:id "2", :label "a", :parent "1"}
 {:id "3", :label "a", :parent "1"}
 {:id "4", :label "a", :parent "0"}
 {:id "5", :label "a", :parent "4"}]

答案 1 :(得分:-1)

这是一种方法。您只需要添加有关为每个节点分配“ id”的部分。

还请注意,您应该重新设置输入数据的格式,以使每个节点都为打ic格式(即,即使没有孩子的单例节点也被包裹在向量中)。

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require [tupelo.core :as t]))

(def tree
  [:a
   [:b
    [:c]
    [:d]]
   [:e
    [:f]]])

(def relationships (atom []))

(defn save-relationships
  [parent-id curr-node]
  (let [curr-id   (first curr-node)
        kid-nodes (rest curr-node)]
    (swap! relationships #(conj % {:parent parent-id :label curr-id}))
    (doseq [kid-node kid-nodes]
      (save-relationships curr-id kid-node))))

(dotest
  (reset! relationships [])
  (save-relationships nil tree)
  (spyx-pretty @relationships))

结果:

~/expr/demo > 
~/expr/demo > lein test

lein test _bootstrap

-------------------------------
   Clojure 1.10.1    Java 12
-------------------------------

lein test tst.demo.core
(clojure.core/deref relationships) => 
[{:parent nil, :label :a}
 {:parent :a, :label :b}
 {:parent :b, :label :c}
 {:parent :b, :label :d}
 {:parent :a, :label :e}
 {:parent :e, :label :f}]

Ran 2 tests containing 0 assertions.
0 failures, 0 errors.