简体   繁体   English

Clojure:如何将函数应用于嵌套映射中的每个值并进行更新?

[英]Clojure: How to apply a function to every value in a nested map and update?

Let's say there is a nested map like below: (partially nested only)假设有一个如下所示的嵌套地图:(仅部分嵌套)

(def mymap {:a 10
        :b {:ba 21, :bb 22 :bc 23}
        :c 30
        :d {:da 41, :db 42}})

How can I apply a function, say #(* % 2) , and update every value in this map?我怎样才能应用一个函数,比如#(* % 2) ,并更新这个地图中的每个值? That is without specifying any key.那就是没有指定任何键。 The result will look like this:结果将如下所示:

{:a 20, 
 :b {:ba 42, :bb 44, :bc 46}, 
 :c 60, 
 :d {:da 82, :db 84}}

So far, I came up with this own function:到目前为止,我想出了这个自己的功能:

(defn map-kv [f coll] (reduce-kv (fn [m k v] (assoc m k (f v))) (empty coll) coll))

But I still need to specify a first-level key and can't apply to all first-level and second-level keys values.但是我仍然需要指定一个一级键,并且不能应用于所有一级和二级键值。

You may wish to review the postwalk function: https://clojuredocs.org/clojure.walk/postwalk您可能希望查看postwalk功能: https : 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}}

Porthos3 makes a good point. Porthos3 提出了一个很好的观点。 The above will transform map keys as well as map values.以上将转换地图键以及地图值。 If you want only values to change, you could use the map-vals function from the Tupelo Clojure library (the Medley lib has a similar function ).如果您只想更改值,您可以使用Tupelo Clojure 库中map-vals函数(Medley 库有一个类似的函数)。

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

(dotest
  (let [data-2     {1 2
                    3 4}
        tx-vals-fn (fn [item]
                     (if (map? item)
                       (t/map-vals item #(* 2 %))
                       item))
        result     (walk/postwalk tx-vals-fn data-2)]
    (is= (spyx result) {1 4, 3 8})))

with result:结果:

-------------------------------
   Clojure 1.10.1    Java 13
-------------------------------

Testing tst.demo.core
result => {1 4, 3 8}

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

In addition to postwalk, as Alan mentioned, it is trivial to recursively explore the map and update every key.除了 postwalk,正如 Alan 提到的,递归探索地图并更新每个键是微不足道的。 Clojure provides a function called fmap that simply applies a function to every value in a map. Clojure 提供了一个名为fmap的函数,它简单地将函数应用于映射中的每个值。 To use:使用:

In project.clj, declare this dependency:在 project.clj 中,声明此依赖项:

[org.clojure/algo.generic "0.1.2"]

And in your code, then require:在你的代码中,然后要求:

(require '[clojure.algo.generic.functor :as f :only [fmap]])

Then define a function that will walk your map recursively:然后定义一个函数来递归地遍历你的地图:

(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}}}

In case you don't want to have to include a non-core function, here's the code for fmap used on a map, from the clojure source (adapted for a defn):如果您不想包含非核心函数,这里是在地图上使用的 fmap 的代码,来自clojure 源(适用于 defn):

(defn fmap [f m]
  (into (empty m) (for [[k v] m] [k (f v)])))

I really like specter, see https://github.com/nathanmarz/specter我真的很喜欢幽灵,请参阅https://github.com/nathanmarz/specter

If you exactly want to change the top 2 levels, calling transform twice is the simplest如果您确实想更改前 2 个级别,则调用 transform 两次是最简单的

(->> mymap 
     (sp/transform [sp/MAP-VALS map? sp/MAP-VALS number?] #(* 2 %))
     (sp/transform [sp/MAP-VALS number?] #(* 2 %)))

You can implement the walk part in specter too, if you really want to replace everything recursively.如果您真的想递归替换所有内容,您也可以在幽灵中实现 walk 部分。 For example, I wanted to floatify all numbers in an arbitrary structure.例如,我想浮动任意结构中的所有数字。 First, I had to define the walker (which also handles vectors, seq, and sets).首先,我必须定义 walker(它还处理向量、seq 和集合)。 This is generic, so I can reuse it.这是通用的,所以我可以重用它。

 (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)

but once I done that, I can implement it is但是一旦我这样做了,我就可以实现它

 (sp/transform [sp/WalkValues integer?] float mymap)

or in this example或者在这个例子中

 (sp/transform [sp/WalkValues number?] #(* 2 %) mymap)
(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))

example:例子:

(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}}

explanation:解释:

postwalk calls walk in its implementation. postwalk在其实现中调用walk

(defn postwalk
  [f form]
  (walk (partial postwalk f) f form))

walk checks the type of the form and it matches the form (a map) against coll? walk检查表单的类型,并将表单(地图)与coll?匹配coll? and then maps inner (which is postwalk with f) against the form which matches map-entry?然后将内部(它是带有 f 的 postwalk) map-entry?map-entry?匹配的表单上map-entry? . .

We don't want to "postwalk with f" against the key so we check to see if it's a map and skip it (return m) if it's not a map.我们不想对键进行“postwalk with f”,所以我们检查它是否是一个地图,如果它不是一个地图,则跳过它(返回 m)。 (This logic fails if you use a map as a key.) (如果您使用地图作为键,则此逻辑失败。)

postwalk passed our f into walk as outer . postwalk通过我们的f进入walk作为outer The lambda inside map-leaves skips calling outer (aka f ) on the resulting maps (look at the coll? match) as it backs out of the recursion.当它退出递归时,map-leaves 中的 lambda 跳过对结果映射(查看coll?匹配)调用outer (又名f )。 The maps were already transformed by the map inner .地图已经被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)))

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM