web-dev-qa-db-fra.com

Mapper une fonction sur les valeurs d'une carte dans Clojure

Je veux transformer une carte de valeurs en une autre carte avec les mêmes clés mais avec une fonction appliquée aux valeurs. Je penserais qu'il y avait une fonction pour faire cela dans l'api clojure, mais je n'ai pas pu le trouver. 

Voici un exemple d'implémentation de ce que je recherche

(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}

Est-ce que quelqu'un sait si map-function-on-map-vals existe déjà? Je penserais que c'était le cas (probablement avec un nom plus joli aussi).

122
Thomas

J'aime bien votre version reduce. Je pense que c'est idiomatique. Voici une version utilisant la compréhension de liste de toute façon.

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

Vous pouvez utiliser le 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}
90
Arthur Edelstein

Voici un moyen assez classique de transformer une carte zipmap prend une liste de clés et une liste de valeurs et "fait ce qu'il faut" pour produire une nouvelle carte Clojure. Vous pouvez également mettre la map autour des touches pour les changer, ou les deux. 

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

ou pour envelopper dans votre fonction:

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

Tiré du livre de cuisine de Clojure, il existe une réduction de kv:

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

Voici une façon assez idiomatique de procéder:

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

Exemple:

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

map-map, map-map-keys et map-map-values

Je ne connais aucune fonction existante dans Clojure pour cela, mais voici une implémentation de cette fonction en tant que map-map-values que vous êtes libre de copier. Il contient deux fonctions étroitement liées, map-map et map-map-keys, qui sont également absentes de la bibliothèque 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) )

Usage

Vous pouvez appeler map-map-values comme ceci:

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

Et les deux autres fonctions sont les suivantes:

(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}

Implémentations alternatives

Si vous ne voulez que map-map-keys ou map-map-values, sans la fonction plus générale map-map, vous pouvez utiliser ces implémentations, qui ne reposent pas sur 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)} )))

En outre, voici une autre implémentation de map-map qui est basée sur clojure.walk/walk au lieu de into , si vous préférez cette formulation:

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

Versions parallèles - pmap-map, etc.

Il existe également des versions parallèles de ces fonctions si vous en avez besoin. Ils utilisent simplement pmap au lieu 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) )
4
Rory O'Kane

Je suis un Clojure n00b, il pourrait donc y avoir des solutions beaucoup plus élégantes. Voilà le mien:

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

La fonction anon crée une petite liste de 2 à partir de chaque touche et de sa valeur fédée. Mapcat exécute cette fonction sur la séquence des touches de la carte et concatène l'ensemble des œuvres dans une grande liste. "apply hash-map" crée une nouvelle carte à partir de cette séquence. Le (% m) peut sembler un peu étrange, il est idiomatique de appliquer une clé à une carte pour rechercher la valeur associée.

Lecture fortement recommandée: The Clojure Cheat Sheet .

2
Carl Smotricz

J'aime votre version reduce. Avec une très légère variation, il peut également conserver le type de structure des enregistrements:

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

Le {} a été remplacé par m. Avec ce changement, les enregistrements restent des enregistrements:

(defrecord Person [firstname lastname])

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

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

En commençant par {}, l’enregistrement perd son recordiness , qu’on peut vouloir conserver, si vous souhaitez utiliser les capacités d’enregistrement (représentation en mémoire compacte, par exemple).

1
olange
(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)))
0
Didier A.