繁体   English   中英

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

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

假设有一个如下所示的嵌套地图:(仅部分嵌套)

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

但是我仍然需要指定一个一级键,并且不能应用于所有一级和二级键值。

您可能希望查看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 提出了一个很好的观点。 以上将转换地图键以及地图值。 如果您只想更改值,您可以使用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})))

结果:

-------------------------------
   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.

除了 postwalk,正如 Alan 提到的,递归探索地图并更新每个键是微不足道的。 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}}}

如果您不想包含非核心函数,这里是在地图上使用的 fmap 的代码,来自clojure 源(适用于 defn):

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

我真的很喜欢幽灵,请参阅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(它还处理向量、seq 和集合)。 这是通用的,所以我可以重用它。

 (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)
(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?匹配coll? 然后将内部(它是带有 f 的 postwalk) map-entry?map-entry?匹配的表单上map-entry? .

我们不想对键进行“postwalk with f”,所以我们检查它是否是一个地图,如果它不是一个地图,则跳过它(返回 m)。 (如果您使用地图作为键,则此逻辑失败。)

postwalk通过我们的f进入walk作为outer 当它退出递归时,map-leaves 中的 lambda 跳过对结果映射(查看coll?匹配)调用outer (又名f )。 地图已经被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