在Clojure中将函数映射到映射的值


138

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

Answers:


153

我喜欢您的reduce版本。我认为这很惯用。无论如何,这是一个使用列表理解的版本。

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

1
我喜欢这个版本,因为如果您了解所有功能以及它所使用的功能,它将非常简短。如果您不这样做,那是学习它们的借口!
2009年

我同意。我不知道in函数,但是在这里使用它非常有意义。
Thomas Thomas

哦,你没看见吗?您正在请客。每当有机会,我都会滥用该功能。如此强大且有用。
2009年

3
我喜欢它,但是参数顺序(除了这个问题之外)有什么理由吗?map由于某些原因,我一直期待[fm] 。
2016年

2
我强烈建议使用(空m),而不要使用{}。因此,它将始终是一种特殊类型的地图。
nickik '19

96

您可以使用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}

我知道这个答案在查看日期时有点晚了,但我认为它是正确的。
edwardsmatt 2011年

这是否使它成为clojure 1.3.0?
AnnanFay 2012年

13
generic.function库已移至一个单独的库: org.clojure/algo.generic "0.1.0" 该示例现在应显示为: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger 2012年

2
任何想法如何fmap在ClojureScript中找到?
sebastibe

37

这是变换地图的一种相当典型的方法。 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))))

1
我不得不为此提供钥匙,这让我很恼火,但是付出的代价并不高。它肯定比我最初的建议好得多。
Thomas

1
我们是否保证键和值以相同的顺序返回相应的值?对于排序图和哈希图?
罗布·拉克兰

4
Rob:是的,键和val将对所有映射使用相同的顺序-与映射上的seq使用相同的顺序。由于哈希映射,排序映射和数组映射都是不可变的,因此在此期间没有机会更改顺序。
Chouser

2
看起来确实是这样,但是在任何地方都有记录吗?至少键和val的文档字符串没有提及这一点。如果我可以指向一些官方文档来保证它会起作用,那么我会更习惯使用它。
Jouni K.Seppänen

1
@Jason是的,我认为这已添加到1.6版的文档中。dev.clojure.org/jira/browse/CLJ-1302
Jouni

23

取自《 Clojure食谱》,有reduce-kv:

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))

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}

2
如果不是明确:匿名功能destructures的键和值来ķv然后返回散列映射映射ķ(IV)
Siddhartha Reddy

6

map-mapmap-map-keysmap-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-keysmap-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) )

1
还要检查棱柱形贴图值函数。使用瞬变github.com/Prismatic/plumbing/blob/…
ClojureMostly 2014年

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的值创建一个2列表。Mapcat在地图按键序列上运行此功能,并将整个作品串联到一个大列表中。“应用哈希映射”从该序列创建一个新映射。(%m)可能看起来有些奇怪,这是惯用的Clojure,用于将键应用于地图以查找关联的值。

强烈推荐阅读:《Clojure备忘单》


我想过要像在示例中所做的那样走入低谷序列。我还喜欢您的名字比我自己的名字更重要:)
Thomas

在Clojure中,关键字是按传递给它们的顺序进行查找的函数。这就是为什么(:keyword a-map)有效的原因。但是,如果键不是关键字,则使用键作为在地图中查找自身的功能将不起作用。因此,您可能希望将上面的(%m)更改为(m%),无论键是什么,它都将起作用。
Siddhartha Reddy

糟糕!谢谢小费,悉达多!
2009年

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

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

从开始,如果您需要记录功能(例如,紧凑的内存表示形式){},则记录将失去其记录性,而该记录性可能想要保留。


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电视上观看此剧集该剧集解释了背后的动机和幽灵的细节。


0

Clojure 1.7( 2015年6月30日发布)为此提供了一个优雅的解决方案update

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

(map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %))
;; => ([:a "TEST"] [:b "TESTING"])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.