Отображение функции по значениям отображения в Clojure

Я хочу преобразовать одну карту значений в другую карту с теми же клавишами, но с функцией, применяемой к значениям. Я бы подумал, что есть функция для этого в api clojure, но я не смог ее найти.

Вот пример реализации того, что я ищу

(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? Я бы подумал, что это произошло (возможно, с более приятным названием).

Ответ 1

Мне нравится ваша версия reduce. Я думаю, что это идиоматично. Здесь версия, использующая переосмысление списка.

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

Ответ 2

Вы можете использовать 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}

Ответ 3

Вот довольно типичный способ преобразования карты. 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))))

Ответ 4

Взятый из Clojure Поваренной книги, есть сокращение-kv:

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

Ответ 5

Здесь достаточно идиоматический способ сделать это:

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

Ответ 6

map-map, map-map-keys и map-map-values

Я не знаю никакой существующей функции в Clojure для этого, но реализует эту функцию как map-map-values, которую вы можете скопировать. Он поставляется с двумя тесно связанными функциями map-map и map-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-keys или 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) )

версии Parellel - 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) )

Ответ 7

Я 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 func делает небольшой 2-лист из каждого ключа и его значение f'ed. Mapcat выполняет эту функцию над последовательностью клавиш карты и объединяет все работы в один большой список. "apply hash-map" создает новую карту из этой последовательности. (% M) может показаться немного странным, идиоматическим Clojure для применения ключа к карте для поиска связанного значения.

Наиболее рекомендуемое чтение: Clojure Cheat Sheet.

Ответ 8

Мне нравится ваша версия 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

Начиная с {}, запись теряет свою записываемость, которую можно сохранить, если вы хотите возможности записи (например, представление компактной памяти).