假设有一个如下所示的嵌套地图:(仅部分嵌套)
(def mymap {:a 10
:b {:ba 21, :bb 22 :bc 23}
:c 30
:d {:da 41, :db 42}})
如何应用函数,比如说(*%2),并更新此映射中的每个值?这没有指定任何键。结果将如下所示:
{:a 20,
:b {:ba 42, :bb 44, :bc 46},
:c 60,
:d {:da 82, :db 84}}
到目前为止,我想出了这个自己的功能:
(defn map-kv [f coll] (reduce-kv (fn [m k v] (assoc m k (f v))) (empty coll) coll))
但是我仍然需要指定第一级键,不能应用于所有第一级和第二级键值。
答案 0 :(得分:6)
您可以查看postwalk
功能:https://clojuredocs.org/clojure.walk/postwalk
(def data
{:a 10
:b {:ba 21, :bb 22 :bc 23}
:c 30
:d {:da 41, :db 42}} )
(defn tx-nums [x]
(if (number? x)
(* 2 x)
x))
(postwalk tx-nums data) =>
{:a 20,
:b {:ba 42, :bb 44, :bc 46},
:c 60,
:d {:da 82, :db 84}}
答案 1 :(得分:3)
除了行走之外,正如艾伦提到的那样,递归地探索地图并更新每个键是微不足道的。 Clojure提供了一个名为fmap
的函数,它只是将函数应用于映射中的每个值。使用:
在project.clj中,声明此依赖项:
[org.clojure/algo.generic "0.1.2"]
在您的代码中,然后要求:
(require '[clojure.algo.generic.functor :as f :only [fmap]])
然后定义一个递归地遍历地图的函数:
(defn fmap*
[f m]
(f/fmap #(if (map? %)
(fmap* f %)
(f %))
m))
(fmap*
(partial * 2) ;; double every number
{:a 21 :b {:x 11 :y 22 :z {:p 100 :q 200}}})
=> {:a 42, :b {:x 22, :y 44, :z {:p 200, :q 400}}}
如果您不想包含非核心功能,请参阅clojure source(适用于defn)的地图上使用的fmap代码:
(defn fmap [f m]
(into (empty m) (for [[k v] m] [k (f v)])))
答案 2 :(得分:2)
我非常喜欢幽灵,请参阅https://github.com/nathanmarz/specter
如果您想要更改前2个级别,则调用transform两次是最简单的
(->> mymap
(sp/transform [sp/MAP-VALS map? sp/MAP-VALS number?] #(* 2 %))
(sp/transform [sp/MAP-VALS number?] #(* 2 %)))
如果您真的想以递归方式替换所有内容,也可以在幽灵中实现walk部分。例如,我想在任意结构中浮动所有数字。首先,我必须定义walker(它也处理vector,seq和sets)。这是通用的,所以我可以重复使用它。
(defprotocolpath WalkValues)
(extend-protocolpath WalkValues
clojure.lang.IPersistentVector [ALL WalkValues]
clojure.lang.IPersistentMap [MAP-VALS WalkValues]
clojure.lang.IPersistentSet [ALL WalkValues]
clojure.lang.ISeq [ALL WalkValues]
Object STAY)
但是一旦我这样做了,我可以实现它
(sp/transform [sp/WalkValues integer?] float mymap)
或在此示例中
(sp/transform [sp/WalkValues number?] #(* 2 %) mymap)
答案 3 :(得分:0)
(require '[clojure.walk :as walk])
(defn fmap [f m]
(into (empty m) (for [[k v] m] [k (f v)])))
(defn map-leaves
[f form]
(walk/postwalk (fn [m]
(if (map? m)
(fmap #(if (map? %) % (f %)) m)
m))
form))
示例:
(map-leaves
(partial * 2)
{:a 10
:b {:ba 21, :bb 22 :bc 23}
:c 30
:d {:da 41, :db 42}})
;; {:a 20, :b {:ba 42, :bb 44, :bc 46}, :c 60, :d {:da 82, :db 84}}
说明:
postwalk
在其实现中调用 walk
。
(defn postwalk
[f form]
(walk (partial postwalk f) f form))
walk
检查表单的类型并将表单(映射)与 coll?
进行匹配,然后将内部(带有 f 的 postwalk)与匹配 map-entry?
的表单进行映射.
我们不想对键进行“postwalk with f”,所以我们检查它是否是一张地图,如果它不是一张地图,则跳过它(返回 m)。 (如果您使用地图作为键,则此逻辑失败。)
postwalk
将我们的 f
作为 walk
传递给 outer
。 map-leaves 中的 lambda 跳过在结果映射上调用 outer
(又名 f
)(查看 coll?
匹配),因为它退出递归。 map inner
已经对地图进行了转换。
(defn walk
[inner outer form]
(cond
(list? form) (outer (apply list (map inner form)))
(map-entry? form)
(outer (MapEntry. (inner (key form)) (inner (val form)) nil))
(seq? form) (outer (doall (map inner form)))
(record? form) (outer (reduce (fn [r x] (conj r (inner x))) form form))
(coll? form) (outer (into (empty form) (map inner form)))
:else (outer form)))