dictionary - conj - Mapeo de una función en los valores de un mapa en Clojure
reduce clojure (8)
Quiero transformar un mapa de valores en otro mapa con las mismas teclas pero con una función aplicada a los valores. Pensaría que había una función para hacer esto en la API de Clojure, pero no he podido encontrarla.
Aquí hay una implementación de ejemplo de lo que estoy buscando
(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}
¿Alguien sabe si map-function-on-map-vals
ya existe? Creo que sí (probablemente con un nombre más agradable también).
map-map
, map-map-keys
, y map-map-values
No conozco ninguna función existente en Clojure para esto, pero aquí hay una implementación de esa función como map-map-values
que puede copiar. Viene con dos funciones estrechamente relacionadas, map-map
y map-map-keys
, que también faltan en la biblioteca estándar:
(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) )
Uso
Puede llamar a map-map-values
esta manera:
(map-map-values str {:a 1 :b 2})
;; => {:a "1", :b "2"}
Y las otras dos funciones como esta:
(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}
Implementaciones alternativas
Si solo quiere map-map-keys
o map-map-values
, sin la función más general de map-map
, puede usar estas implementaciones, que no se basan en 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)} )))
Además, aquí hay una implementación alternativa de map-map
que se basa en clojure.walk/walk
lugar de into
, si prefieres este fraseo:
(defn map-map [f m]
(clojure.walk/walk #(apply f %) identity m) )
Versiones de Parellel - pmap-map
, etc.
También hay versiones paralelas de estas funciones si las necesita. Simplemente usan pmap
lugar de 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) )
Aquí hay una forma bastante idiomática de hacer esto:
(defn map-function-on-map-vals [m f]
(apply merge
(map (fn [[k v]] {k (f v)})
m)))
Ejemplo:
user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}
Aquí hay una forma bastante típica de transformar un mapa. zipmap
toma una lista de claves y una lista de valores y "hace lo correcto" produciendo un nuevo mapa de Clojure. También podría colocar el map
alrededor de las teclas para cambiarlas, o ambas cosas.
(zipmap (keys data) (map #(do-stuff %) (vals data)))
o para envolverlo en tu función:
(defn map-function-on-map-vals [m f]
(zipmap (keys m) (map f (vals m))))
Me gusta tu versión reducida muy bien. Creo que es idiomático Aquí hay una versión que usa la lista de comprensión de todos modos.
(defn foo [m f]
(into {} (for [[k v] m] [k (f v)])))
Me gusta tu versión reducida. Con una ligera variación, también puede conservar el tipo de estructuras de registros:
(defn map-function-on-map-vals [m f]
(reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))
{}
Fue reemplazado por m
. Con ese cambio, los registros siguen siendo registros:
(defrecord Person [firstname lastname])
(def p (map->Person {}))
(class p) ''=> Person
(class (map-function-on-map-vals p
(fn [v] (str v)))) ''=> Person
Al comenzar con {}
, el registro pierde su capacidad de grabar , que es posible que desee retener, si desea las capacidades de grabación (por ejemplo, representación de memoria compacta).
Puede usar 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}
Soy Clojure n00b, por lo que puede haber soluciones mucho más elegantes. Aquí está el mío:
(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))
El anon func hace una pequeña lista de 2 de cada tecla y su valor f''ed. Mapcat ejecuta esta función sobre la secuencia de las claves del mapa y concatena todo el trabajo en una sola lista grande. "aplicar hash-map" crea un nuevo mapa a partir de esa secuencia. El (% m) puede parecer un poco raro, es Clojure idiomático para aplicar una clave a un mapa para buscar el valor asociado.
Lectura más recomendada: La Hoja de trucos de Clojure .
Tomado del libro de cocina Clojure, hay reduce-kv:
(defn map-kv [m f]
(reduce-kv #(assoc %1 %2 (f %3)) {} m))