在 Clojure 映射一个关于地图值的函数

我想将一个值映射转换为另一个具有相同键但是应用了一个函数的值映射。我认为在 clojure api 中有这样做的函数,但我一直找不到它。

这里有一个我正在寻找的示例实现

(defn map-function-on-map-vals [m f]
(reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) {} m))
(println (map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %)))
{:b TESTING, :a TEST}

有人知道 map-function-on-map-vals是否已经存在吗? 我认为它已经存在了(可能还有一个更好的名字)。

56880 次浏览

我是一个 Clojure n00b,所以可能有更优雅的解决方案:

(def example {:a 1 :b 2 :c 3 :d 4})
(def func #(* % %))


(prn example)


(defn remap [m f]
(apply hash-map (mapcat #(list % (f (% m))) (keys m))))


(prn (remap example func))

匿名 func 从每个键及其 f’值创建一个小的2-list。Mapcat 在地图键的顺序上运行这个函数,并将整个作品连接到一个大列表中。“ application hash-map”从该序列创建一个新的 map。(% m)可能看起来有点奇怪,它是一种惯用的 Clojure,用于将一个键应用于映射以查找相关的值。

最强烈推荐阅读: Clojure 小抄

下面是一个相当典型的转换映射的方法。 zipmap 获取一个键列表和一个值列表,并“做正确的事情”生成一个新的 Clojure 映射。您也可以将 map放在键的周围来更改它们,或者两者兼而有之。

(zipmap (keys data) (map #(do-stuff %) (vals data)))

或者把它包装在你的函数中:

(defn map-function-on-map-vals [m f]
(zipmap (keys m) (map f (vals m))))

我喜欢你的 ABc0版本,我觉得它很地道,这个版本用的是列表内涵。

(defn foo [m f]
(into {} (for [[k v] m] [k (f v)])))

这里有一个相当惯用的方法:

(defn map-function-on-map-vals [m f]
(apply merge
(map (fn [[k v]] {k (f v)})
m)))

例如:

user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}

你可以使用 clojure.algo.generic.functor/fmap:

user=> (use '[clojure.algo.generic.functor :only (fmap)])
nil
user=> (fmap inc {:a 1 :b 3 :c 5})
{:a 2, :b 4, :c 6}

map-mapmap-map-keys,和 map-map-values

我知道在 Clojure 还没有这样的功能,但是这里有一个实现这个功能的实例,你可以自由复制。它附带了两个紧密相关的函数,map-mapmap-map-keys,标准库中也没有这两个函数:

(defn map-map
"Returns a new map with each key-value pair in `m` transformed by `f`. `f` takes the arguments `[key value]` and should return a value castable to a map entry, such as `{transformed-key transformed-value}`."
[f m]
(into (empty m) (map #(apply f %) m)) )


(defn map-map-keys [f m]
(map-map (fn [key value] {(f key) value}) m) )


(defn map-map-values [f m]
(map-map (fn [key value] {key (f value)}) m) )

用法

你可以这样调用 map-map-values:

(map-map-values str {:a 1 :b 2})
;;           => {:a "1", :b "2"}

另外两个函数是这样的:

(map-map-keys str {:a 1 :b 2})
;;         => {":a" 1, ":b" 2}
(map-map (fn [k v] {v k}) {:a 1 :b 2})
;;    => {1 :a, 2 :b}

替代实现

如果你只想要 map-map-keys或者 map-map-values,没有更通用的 map-map函数,你可以使用这些不依赖于 map-map的实现:

(defn map-map-keys [f m]
(into (empty m)
(for [[key value] m]
{(f key) value} )))


(defn map-map-values [f m]
(into (empty m)
(for [[key value] m]
{key (f value)} )))

另外,如果你喜欢这样的措辞,这里还有一个 map-map的替代实现,它基于 clojure.walk/walk而不是 into:

(defn map-map [f m]
(clojure.walk/walk #(apply f %) identity m) )

并行版本-pmap-map等。

如果你需要,这些函数也有并行版本。它们只是使用 pmap而不是 map

(defn pmap-map [f m]
(into (empty m) (pmap #(apply f %) m)) )
(defn pmap-map-keys [f m]
(pmap-map (fn [key value] {(f key) value}) m) )
(defn pmap-map-values [f m]
(pmap-map (fn [key value] {key (f value)}) m) )

我喜欢你的 reduce版本,只要稍作改动,它也可以保留记录结构的类型:

(defn map-function-on-map-vals [m f]
(reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))

{}m所取代。随着这一变化,记录保持着记录:

(defrecord Person [firstname lastname])


(def p (map->Person {}))
(class p) '=> Person


(class (map-function-on-map-vals p
(fn [v] (str v)))) '=> Person

{}开始,记录就失去了它的 记录,如果需要记录功能(例如紧凑内存表示) ,可能需要保留这个 记录

取自 Clojure Cookbook 的 reduce-kv:

(defn map-kv [m f]
(reduce-kv #(assoc %1 %2 (f %3)) {} m))
(defn map-vals
"Map f over every value of m.
Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
f is a function of one arg, which will be called which each value of m, and should return the new value.
Faster then map-vals-transient on small maps (8 elements and under)"
[f m]
(reduce-kv (fn [m k v]
(assoc m k (f v)))
{} m))


(defn map-vals-transient
"Map f over every value of m.
Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
f is a function of one arg, which will be called which each value of m, and should return the new value.
Faster then map-vals on big maps (9 elements or more)"
[f m]
(persistent! (reduce-kv (fn [m k v]
(assoc! m k (f v)))
(transient {}) m)))

我想知道为什么还没有人提到 幽灵库。编写它的目的是使这种转换易于编码(更重要的是,编写的代码易于理解) ,同时仍然具有很高的性能:

(require '[com.rpl.specter :as specter])


(defn map-vals [m f]
(specter/transform
[specter/ALL specter/LAST]
f m))


(map-vals {:a "test" :b "testing"}
#(.toUpperCase %))

用纯 Clojure 编写这样一个函数很简单,但是一旦转向由不同数据结构组成的高度嵌套的代码,代码就变得更加复杂了。这就是 幽灵的用武之地。

我推荐观看 Clojure 电视台的 这一集节目,其中解释了 幽灵背后的动机和细节。

Clojure 1.7(释放 June 30,2015)使用 update提供了一个优雅的解决方案:

(defn map-function-on-map-vals [m f]
(->> (map #(update % 1 f) m)
(into {})))


(map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %))
;; => {:a "TEST", :b "TESTING"}

Clojure 1.11 为标准库 clojure.core增加了一个函数,Clojure 1.11发布了 2022-03-22

update-vals

(update-vals m f)将函数应用到映射中的每个值。它返回一个新的映射 {k (f v) ...}

用法

(update-vals {:a 1 :b 2} str)
;;        => {:a "1", :b "2"}

请参阅: http://clojure.github.io/clojure/clojure.core-api.html # clojure.core/update-keys”rel = “ noReferrer”> update-keys

(update-keys m f)将该函数应用于地图中的每个键。它返回一个新的地图 {(f k) v ...}。由 (f k)产生的所有键必须是唯一的。

这个函数也是在 Clojure 1.11中添加的。

用法

(update-keys {:a 1 :b 2} str)
;;        => {":a" 1, ":b" 2}