【问题标题】:Clojure - Recursively Semi-Flatten Nested MapClojure - 递归半扁平嵌套映射
【发布时间】:2016-08-12 07:58:03
【问题描述】:

在clojure中,我怎样才能像这样转一个嵌套的地图:

(def parent {:id "parent-1"
             :value "Hi dude!"
             :children [{:id "child-11"
                         :value "How is life?"
                         :children [{:id "child-111"
                                     :value "Some value"
                                     :children []}]}
                        {:id "child-12"
                         :value "Does it work?"
                         :children []}]})

进入这个:

[
[{:id "parent-1", :value "Hi dude!"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"} {:id "child-111", :value "Some value"}]
[{:id "parent-1", :value "Hi dude!"} {:id "child-12", :value "Does it work?"}]
]

我在非常糟糕的递归尝试中磕磕绊绊,现在我的大脑已经筋疲力尽了。

到目前为止,我所得到的如下。它确实得到了正确的数据,但是它把数据放在了一些额外的不需要的嵌套向量中。

如何解决这个问题? 在 Clojure 中有没有一种很好的惯用方式来做到这一点?

谢谢。

(defn do-flatten [node parent-tree]
  (let [node-res (conj parent-tree (dissoc node :children))
        child-res (mapv #(do-flatten % node-res) (:children node))
        end-res (if (empty? child-res) [node-res] [node-res child-res])]
    end-res))

(do-flatten parent [])

产生:

[
[{:id "parent-1", :value "Hi dude!"}] 
[[
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"}]
[[
[{:id "parent-1", :value "Hi dude!"} {:id "child-11", :value "How is life?"} {:id "child-111", :value "Some value"}]
]]]
[
[{:id "parent-1", :value "Hi dude!"} {:id "child-12", :value "Does it work?"}]
]]
]

【问题讨论】:

  • 您能描述一下这次重新格式化的目标吗?
  • 通过这个谜题,我试图更好地了解如何在 Clojure 中最好地处理数据转换。

标签: clojure


【解决方案1】:

我不知道这是否惯用,但它似乎有效。

(defn do-flatten
  ([node]
   (do-flatten node []))
  ([node parents]
   (let [path (conj parents (dissoc node :children))]
     (vec (concat [path] (mapcat #(do-flatten % path)
                                 (:children node)))))))

您可以在调用时省略[]

【讨论】:

  • 非常感谢您的快速回复! mapcat 和 concat 的组合确实可以解决问题。
【解决方案2】:

另一种选择是使用拉链:

(require '[clojure.zip :as z])

(defn paths [p]
  (loop [curr (z/zipper map? :children nil p)
         res []]
    (cond (z/end? curr) res
          (z/branch? curr) (recur (z/next curr)
                                  (conj res
                                        (mapv #(select-keys % [:id :value])
                                              (conj (z/path curr) (z/node curr)))))
          :else (recur (z/next curr) res))))

【讨论】:

    【解决方案3】:

    我倾向于使用一些本地状态来简化逻辑:

    (defn do-flatten
      ([node]
       (let [acc (atom [])]
         (do-flatten node [] acc)
         @acc))
      ([node base acc]
       (let [new-base (into base (self node))]
         (swap! acc conj new-base)
         (doall
          (map #(do-flatten % new-base acc) (:children node))))))
    

    也许某些功能纯粹主义者会不喜欢它,当然您可以以纯功能的方式完成整个事情。我的感觉是它是一个临时且完全本地的状态(因此不会导致状态臭名昭著的那种问题),所以如果它提高了可读性(我认为确实如此),我'我很乐意使用它。

    【讨论】:

    • 我认为您可能想要使用 reduce 以便您可以访问先前的状态/累加器来工作,而不是使用本地化原子。甚至可以利用瞬变来提高速度。
    • @ScottMitchell 这似乎也很合理。
    猜你喜欢
    • 1970-01-01
    • 2015-01-28
    • 2016-09-11
    • 2020-10-09
    • 2016-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    相关资源
    最近更新 更多