Clojure - 走路径

时间:2015-11-08 13:15:26

标签: dictionary data-structures clojure clojurescript

我正在寻找一个类似于clojure.walk中具有inner函数的函数的函数:

  • 不是键和值,就像clojure.walk / walk函数
  • 一样
  • 但是从顶级数据结构访问值所需的键向量。
  • 以递归方式遍历所有数据

示例:

;; not good since it takes `[k v]` as argument instead of `[path v]`, and is not recursive.
user=> (clojure.walk/walk (fn [[k v]] [k (* 10 v)]) identity {:a 1 :b {:c 2}})
;; {:a 10, :c 30, :b 20}

;; it should receive as arguments instead :
[[:a] 1]
[[:b :c] 2]

注意:

  • 它也应该使用数组,使用键0,1,2 ......(就像在get-in中一样)。
  • 如果允许简化代码,我真的不关心outer参数。

3 个答案:

答案 0 :(得分:8)

目前学习clojure,我试过这个练习。 然而,我发现将它直接实现为沿着应用内部函数的树向下执行它是非常棘手的。

为了达到你想要的结果,我将任务分成2:

  • 首先将嵌套结构转换为字典,路径为键,值为
  • 然后将内部函数映射,或者使用外部函数进行缩小。

我的实施:

;; Helper function to have vector's indexes work like for get-in
(defn- to-indexed-seqs [coll]
  (if (map? coll)
    coll
    (map vector (range) coll)))

;; Flattening the tree to a dict of (path, value) pairs that I can map over
;; user>  (flatten-path [] {:a {:k1 1 :k2 2} :b [1 2 3]})
;; {[:a :k1] 1, [:a :k2] 2, [:b 0] 1, [:b 1] 2, [:b 2] 3}
(defn- flatten-path [path step]
  (if (coll? step)
    (->> step
         to-indexed-seqs
         (map (fn [[k v]] (flatten-path (conj path k) v)))
         (into {}))
    [path step]))

;; Some final glue
(defn path-walk [f coll]
  (->> coll
      (flatten-path [])
      (map #(apply f %))))

;; user> (println (clojure.string/join "\n" (path-walk #(str %1 " - " %2) {:a {:k1 1 :k2 2} :b [1 2 3]})))
;; [:a :k1] - 1
;; [:a :k2] - 2
;; [:b 0] - 1
;; [:b 1] - 2
;; [:b 2] - 3

答案 1 :(得分:0)

事实证明,Stuart Halloway发布了a gist可能有一些用处(它使用协议,这使得它也可以扩展):

(ns user)

(def app
  "Intenal Helper"
  (fnil conj []))

(defprotocol PathSeq
  (path-seq* [form path] "Helper for path-seq"))

(extend-protocol PathSeq
  java.util.List
  (path-seq*
   [form path]
   (->> (map-indexed
         (fn [idx item]
           (path-seq* item (app path idx)))
         form)
        (mapcat identity)))

  java.util.Map
  (path-seq*
   [form path]
   (->> (map
         (fn [[k v]]
           (path-seq* v (app path k)))
         form)
        (mapcat identity)))

  java.util.Set
  (path-seq*
   [form path]
   (->> (map
         (fn [v]
           (path-seq* v (app path v)))
         form)
        (mapcat identity)))


  java.lang.Object
  (path-seq* [form path] [[form path]])

  nil
  (path-seq* [_ path] [[nil path]]))

(defn path-seq
  "Returns a sequence of paths into a form, and the elements found at
   those paths.  Each item in the sequence is a map with :path
   and :form keys. Paths are built based on collection type: lists
   by position, maps by key, and sets by value, e.g.

   (path-seq [:a [:b :c] {:d :e} #{:f}])

   ({:path [0], :form :a}
    {:path [1 0], :form :b}
    {:path [1 1], :form :c}
    {:path [2 :d], :form :e}
    {:path [3 :f], :form :f})
   "
  [form]
  (map
   #(let [[form path] %]
      {:path path :form form})
   (path-seq* form nil)))

(comment
  (path-seq [:a [:b :c] {:d :e} #{:f}])

  ;; finding nils hiding in data structures:
  (->> (path-seq [:a [:b nil] {:d :e} #{:f}])
       (filter (comp nil? :form)))

  ;; finding a nil hiding in a Datomic transaction
  (->> (path-seq {:db/id 100
                  :friends [{:firstName "John"}
                            {:firstName nil}]})
       (filter (comp nil? :form)))


  )

注意:在我的情况下,我也可以使用Specter,所以如果您正在阅读本文,您可能也想查看它。

答案 2 :(得分:0)

还有https://github.com/levand/contextual/

(def node (:b (first (root :a))))

(= node {:c 1}) ;; => true

(c/context node) ;; => [:a 0 :b]