Mapping di una funzione sui valori di una mappa in Clojure
-
16-09-2019 - |
Domanda
Voglio trasformare una mappa di valori ad un altro mappa con le stesse chiavi, ma con una funzione applicata ai valori. Vorrei che ci fosse una funzione per fare questo nel api clojure, ma sono stato in grado di trovarlo.
Ecco un esempio di implementazione di quello che sto cercando
(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}
Qualcuno sa se map-function-on-map-vals
esiste già? Vorrei pensare che ha fatto (probabilmente con un nome più bello troppo).
Soluzione
Mi piace la versione reduce
bene. Penso che sia idiomatica. Ecco una versione che utilizza di lista in ogni modo.
(defn foo [m f]
(into {} (for [[k v] m] [k (f v)])))
Altri suggerimenti
È possibile utilizzare il 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}
Ecco un modo abbastanza tipico per trasformare una mappa.
zipmap
prende una lista di chiavi e un elenco di valori e "fa la cosa giusta" la produzione di una nuova mappa Clojure. Si potrebbe anche mettere il map
intorno ai tasti di modificarli, o entrambi.
(zipmap (keys data) (map #(do-stuff %) (vals data)))
o ad avvolgerlo nella funzione:
(defn map-function-on-map-vals [m f]
(zipmap (keys m) (map f (vals m))))
Preso dal Clojure Cookbook, c'è kv ridurre-:
(defn map-kv [m f]
(reduce-kv #(assoc %1 %2 (f %3)) {} m))
Ecco un modo abbastanza idiomatico per fare questo:
(defn map-function-on-map-vals [m f]
(apply merge
(map (fn [[k v]] {k (f v)})
m)))
Esempio:
user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}
map-map
, map-map-keys
, e map-map-values
Non conosco alcuna funzione esistente in Clojure per questo, ma qui è un'implementazione di tale funzione come map-map-values
che si è liberi di copiare. Viene fornito con due funzioni strettamente correlate, map-map
e map-map-keys
, che sono anche mancano dalla libreria standard:
(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
È possibile chiamare map-map-values
in questo modo:
(map-map-values str {:a 1 :b 2})
;; => {:a "1", :b "2"}
E le altre due funzioni in questo modo:
(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}
implementazioni alternative
Se desideri solo map-map-keys
o map-map-values
, senza la funzione più generale map-map
, è possibile utilizzare queste implementazioni, che non si basano su 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)} )))
Inoltre, ecco un'implementazione alternativa di map-map
che si basa su clojure.walk/walk
invece di < a href = "http://clojuredocs.org/clojure_core/clojure.core/into" rel = "nofollow"> into
, se si preferisce questo fraseggio:
(defn map-map [f m]
(clojure.walk/walk #(apply f %) identity m) )
versioni Parellel -. pmap-map
, ecc
Ci sono anche le versioni parallele di queste funzioni, se ne avete bisogno. Essi utilizzano semplicemente pmap
invece di 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) )
Sono un n00b Clojure, quindi ci potrebbe essere molto più eleganti soluzioni. Ecco la mia:
(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))
Il func anon fa un po 'di 2-list da ogni chiave e il suo valore f'ed. Mapcat gestisce questa funzione sopra la sequenza di tasti della mappa e concatena il tutto funziona in un unico grande lista. "Applicare hash-map" crea una nuova mappa da quella sequenza. L'(% m) può sembrare un po 'strano, è idiomatica Clojure per l'applicazione di una chiave per una mappa per cercare il valore associato.
lettura più altamente raccomandato:. La Clojure Cheat Sheet
Mi piace la versione reduce
. Con una leggera variazione, può anche mantenere il tipo di strutture record:
(defn map-function-on-map-vals [m f]
(reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))
Il {}
è stato sostituito da m
. Con questo cambiamento, i record rimangono record:
(defrecord Person [firstname lastname])
(def p (map->Person {}))
(class p) '=> Person
(class (map-function-on-map-vals p
(fn [v] (str v)))) '=> Person
Iniziando con {}
, il record perde la sua recordiness , che si potrebbe desiderare mantenere, se volete le funzionalità di registrazione (rappresentazione di memoria compatta per esempio).
(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)))