简体   繁体   English

如何在 Clojure 中将有序树转换为命名节点的集合?

[英]How can I turn an ordered tree into a collection of named nodes in Clojure?

I think it's best to use an example.我认为最好使用一个例子。 Let's say I have an ordered tree:假设我有一个有序的树:

(def abcd [:a [:b :c] :d])

I want to build from it a collection of key-value maps, each map representing a nodes of this tree, with a random name and all relevant information, that is, its parent (nil for the root node) its index (0, 1, 2 ..) and, if it's a leaf node, its content (like ":a").我想从中构建一个键值映射集合,每个映射代表这棵树的一个节点,具有随机名称和所有相关信息,即其父节点(根节点为零)其索引 (0, 1 , 2 ..) 并且,如果它是叶节点,则其内容(如“:a”)。 For instance, in this case it could be:例如,在这种情况下,它可能是:

[{:name G__36654, :parent nil, :index 0}
 {:name G__36655, :content :a, :parent G__36654, :index 0}
 {:name G__36656, :parent G__36654, :index 1}
 {:name G__36657, :content :b, :parent G__36656, :index 0}
 {:name G__36658, :content :c, :parent G__36656, :index 1}
 {:name G__36659, :content :d, :parent G__36654, :index 2}]

I defined a function that seems to do what I want, but it uses recursion by calling itself and I'm having trouble figuring out how to use loop-recur instead, and I believe there must be something better out there.我定义了一个函数,它似乎可以做我想做的事,但它通过调用自身来使用递归,我无法弄清楚如何使用 loop-recur 来代替,我相信那里一定有更好的东西。 Here's my attempt:这是我的尝试:

(defn mttrav "my tree traversal"
  ([ptree parent index]
   (let [name (gensym)]
     (cond
       (not (coll? ptree)) [ {:name name :content ptree :parent parent :index index}]

       :else (reduce into
                     [{:name name  :parent parent :index index}]
                     (map-indexed #(mttrav %2 name  %1) ptree)))))
  ([ptree]
   (mttrav ptree nil  0)))

BTW, I don't know if a vector is the right collection to use, maybe a set would make more sense, but I'm using a vector for easier debugging, since it's more readable when the order in which nodes are generated is preserved, and if nodes are accidentally repeated I want to see it.顺便说一句,我不知道向量是否适合使用集合,也许集合会更有意义,但我使用向量来更容易调试,因为当节点生成的顺序被保留时它更具可读性,如果节点不小心重复,我想看到它。

Thanks in advance!提前致谢!

Edit: just to clarify, it would also be acceptable for each node to have a list of :child nodes instead of a :parent node, and some other variations, as long as it's a flat collection of maps, each map representing a node, with a unique :name, and the position, content and parent-child relations of the nodes are captured in this structure.编辑:只是为了澄清,每个节点都有一个 :child 节点列表而不是 :parent 节点和其他一些变体也是可以接受的,只要它是地图的平面集合,每个地图代表一个节点,具有唯一的 :name,节点的位置、内容和父子关系都在此结构中捕获。 The intended input are hiccup parse trees coming typically from Instaparse, and the maps are meant to become records to insert in a Clara session.预期的输入是通常来自 Instaparse 的 hiccup 解析树,这些映射旨在成为要插入 Clara 会话的记录。

When a tree resists tail recursion, another thing to try is a "zipper" from Clojure's standard library.当一棵树抵抗尾递归时,另一件要尝试的事情是来自 Clojure 标准库的“拉链”。 Zippers shine for editing, but they're also pretty good at linearizing depth-first traversal while keeping structure context available.拉链很适合编辑,但它们也非常擅长线性化深度优先遍历,同时保持结构上下文可用。 A typical zipper loop looks like this:一个典型的拉链环如下所示:

user> (def abcd '(:a (:b :c) :d))
#'user/abcd'
user> (loop [ret [], z (zip/seq-zip abcd)] 
        (if (zip/end? z)
          ret
          (let [o {:name 42, :content (zip/node z), :parent 42, :index 42}]
            (recur (conj ret o) (zip/next z)))))
[{:name 42, :content (:a (:b :c) :d), :parent 42, :index 42}
 {:name 42, :content :a, :parent 42, :index 42}
 {:name 42, :content (:b :c), :parent 42, :index 42}
 {:name 42, :content :b, :parent 42, :index 42}
 {:name 42, :content :c, :parent 42, :index 42}
 {:name 42, :content :d, :parent 42, :index 42}]

To fill in :parent and :index , you'll find zipper notation for looking "up" at parents, "left" for siblings, etc., in the official docs at https://clojure.github.io/clojure/clojure.zip-api.html .要填写:parent:index ,您会在https://clojure.github.io/clojure/clojure的官方文档中找到用于“向上”查找父母、“向左”查找兄弟姐妹等的拉链符号.zip-api.html

I created the zip with seq-zip having modeled nodes as a list.我使用seq-zip创建了 zip,将节点建模为列表。 Your specific case models nodes as vectors , which seq-zip does not recognize, so you would presumably use vector-zip or invent your own adapter.您的具体情况下的模型节点作为载体,其中seq-zip不承认,所以你大概会用vector-zip或创造自己的适配器。 You can follow the "Source" link in the docs to see how seq-zip and vector-zip work.您可以按照文档中的“源”链接查看seq-zipvector-zip工作的。

Breadth first traversal is what you need.广度优先遍历正是你所需要的。 So if you want to build the list of parents while you traverse the tree, you need to first uniquely identify all your leaf nodes.因此,如果要在遍历树的同时构建父节点列表,则需要首先唯一标识所有叶节点。 I'm not sure it can be done without doing that, except if you know for sure that your leafs nodes are unique.我不确定不这样做是否可以完成,除非您确定您的叶子节点是唯一的。 It's also getting really late/early here, so my brain is not working optimally.这里也很晚/很早,所以我的大脑没有以最佳方式工作。 I'm sure my solution can get distilled down a lot.我相信我的解决方案可以得到很多提炼。

So if you have a tree like [:a [:b :c] :d [:b :c]], [:b :c] is a parent of :b and :c, but then last two leaf nodes are also :b and :c, so which parent do you choose ?所以如果你有一个像 [:a [:b :c] :d [:b :c]] 的树,[:b :c] 是 :b 和 :c 的父节点,但是最后两个叶节点也是:b 和 :c,那么你选择哪个家长?

So let's have a tree whose leaves have unique id.所以让我们有一棵树,它的叶子有唯一的 id。

(defn attach-ids [tree]
  (clojure.walk/postwalk (fn [node]
                           (if (coll? node) node
                               {:node node :id (gensym)}))
                         tree))


(def tree (attach-ids [:a [:b :c] :d]))

;; produces this
;; [{:node :a, :id G__21500}
;; [{:node :b, :id G__21501} {:node :c, :id G__21502}]
;;  {:node :d, :id G__21503}]

Now for the rest of the solution现在是解决方案的其余部分


(defn add-parent [parent-map id branch]
  (assoc parent-map id {:children-ids (set (map :id branch))
                        :child-nodes (map :node branch)}))


(defn find-parent-id [node parent-map]
  (->> parent-map
    (filter (fn [[parent-id {children-ids :children-ids}]]
              (contains? children-ids (:id node))))
    ffirst))


(defn find-index [node parent-map tree]
  (if-let [parent-id (find-parent-id node parent-map)]
    (let [children (:child-nodes (get parent-map parent-id))]
      (.indexOf children (:node node)))
    (.indexOf tree node)))


(defn bfs [tree]
  (loop [queue tree
         parent-map {}
         ret []]
    (if (not-empty queue)
      (let [node (first queue)
            rst (vec (rest queue))]
        (cond
          (map? node)
          (recur rst
                 parent-map
                 (conj ret (assoc node :parent (find-parent-id node parent-map) 
                                       :index (find-index node parent-map tree))))
          (vector? node)
          (let [parent-id (gensym)]
            (recur (into rst node)
                   (add-parent parent-map parent-id node)
                   (conj ret {:id parent-id 
                              :index (find-index node parent-map tree)
                              :parent (find-parent-id node parent-map)})))))
      ret)))




(def tree  (attach-ids [:a [:b :c] :d]))
(bfs tree)

;; children with :parent nil value point to root
;;[{:node :a, :id G__21504, :parent nil, :index 0}
;; {:id G__21513, :index 1}
;; {:node :d, :id G__21507, :parent nil, :index 2}
;; {:node :b, :id G__21505, :parent G__21513, :index 0}
;; {:node :c, :id G__21506, :parent G__21513, :index 1}]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在Clojure中递归计算树结构的深度? - How can i recursively calculate the depth of a tree structure in Clojure? 如何将节点树的数据转换为有序的ArrayList? - How do I convert the data of a Node tree into an ordered ArrayList? 如何制作树状结构,其中节点是 Rust 中的特征对象? - How can I make tree-like structure where the nodes are trait objects in Rust? 如何在C中的二进制搜索树中删除节点级别? - How do I delete a level of nodes in a Binary Search Tree in C? 在 Haskell 中,如何返回二叉树中的节点数? - In Haskell, how do I return the number of nodes in a binary tree? 在树结构中,如何命名树,节点,叶子? - In a tree structure, how to name the tree, the nodes, the leaves? 我怎样才能把这个递归代码变成迭代的? - how can i turn this recursive code into iterative? 如何打印树的所有节点? - How to print all nodes of a tree? 关于树数据结构的问题:我们如何填充所有树节点的所有有序后继指针? - Question on Tree Data Structure: How can we fill all inorder successor pointer of all tree nodes? 如何在Clojure中递归展平任意嵌套的矢量和地图? - how can I recursively flatten arbitrarily nested vectors and maps in Clojure?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM