142

我想将一个值映射转换为另一个具有相同键但具有应用于值的函数的映射。我认为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已经存在吗?我认为确实如此(可能也有一个更好的名字)。

4

11 回答 11

166

我喜欢你的reduce版本就好了。我认为这是惯用语。无论如何,这是一个使用列表推导的版本。

(defn foo [m f]
  (into {} (for [[k v] m] [k (f v)])))
于 2009-11-05T02:16:29.247 回答
98

您可以使用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}
于 2010-09-21T05:44:05.913 回答
41

这是转换地图的一种相当典型的方法。 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))))
于 2009-11-04T22:18:17.037 回答
24

取自 Clojure Cookbook,有 reduce-kv:

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))
于 2014-10-22T18:46:37.150 回答
8

这是一种相当惯用的方法:

(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}
于 2009-11-05T05:09:33.903 回答
6

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

我知道 Clojure 中没有为此的现有函数,但这是该函数的一个实现,因为map-map-values您可以自由复制。它带有两个密切相关的函数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-keysor 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) )
于 2013-11-24T05:49:19.700 回答
2

我是 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))

anon 函数从每个键及其 f'ed 值中生成一个小 2 列表。Mapcat 在地图键的序列上运行此函数,并将整个作品连接到一个大列表中。“应用哈希映射”从该序列创建一个新映射。(% m) 可能看起来有点奇怪,它是惯用的 Clojure,用于将键应用于映射以查找关联的值。

最强烈推荐阅读:Clojure 备忘单

于 2009-11-04T22:12:37.873 回答
2
(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)))
于 2018-11-12T04:50:40.733 回答
1

我喜欢你的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

从 开始{},记录会失去它的记录性,如果您需要记录功能(例如紧凑的内存表示),则可能希望保留它。

于 2014-10-17T20:10:43.653 回答
0

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

(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 TV 上观看这一集,它解释了spectre背后的动机和细节。

于 2020-01-10T10:20:19.847 回答
0

Clojure 1.7(2015年 6 月 30 日发布)为此提供了一个优雅的解决方案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"}
于 2020-04-13T22:07:13.633 回答