2013-03-25 2 views
2

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

(defn transform-map [m fm] 
    (into {} (for [[k v] m] 
    (let [mfn (get fm k identity)] [k (mfn v)]))) 

(def m {:a 0 :b 0 :c 0 :d 0}) 
(def fm {:a inc :b dec :c identity}) 
(transform-map m fm) ;=> {:a 1, :b -1, :c 0, :d 0} 

Это прекрасно работает, но только до тех пор, пока каждая функция принимает один аргумент, который является текущим значением ключа. Что делать, если я хочу поместить функцию в свою карту функций, которая использует значения, отличные от тех, которые находятся в одном ключе? Например, предположим, что я хочу поместить сумму ключей :a и :b в ключ :d?

я могу попробовать что-то вроде:

(assoc fm :d (fn[a b] (+ a b))) 

но есть способ, которым я могу изменить мою transform-map функции так, он будет использовать соответствующие аргументы в этом вызове функции?

ответ

4

Я хотел бы предложить, чтобы разложить функции и как они применимы в карте преобразования. Ниже приведен пример, чтобы показать, что:

;; functions which take some params and return a result 
(defn sub [a b] (- a b)) 
(defn add [a b] (+ a b)) 

;; data map 
(def data {:a 5 :b 4 :c 3}) 

;; transformation map key => output key, value is vector of function 
;; to apply and the params that function will take from the data map 
(def transformations {:a [inc :a] 
         :b [dec :b] 
         :c [add :a :b] 
         :d [sub :b :c]}) 

; The transformation function 
(defn transform-map [m fm] 
    (into {} (map (fn [[k v]] 
        [k (apply (first v) 
          ((apply juxt (rest v)) m))]) 
       fm))) 

(transform-map data transformations) 
+0

Мне нравится этот подход, @Ankur. Один недостаток, IMO, хотя заключается в том, что, поскольку вы «сопоставляете» по файлу 'fm', вы не можете автоматически передавать ключи в' m', которые не имеют ключей в 'fm' для вывода. Это довольно легко исправить, хотя «слияние» с отсутствующими ключами. Благодаря! – stand

+0

Yup, в приведенной выше реализации, 'transformations' решает ключи выходной карты. – Ankur

+0

Эй, @ Анкур, отличный ответ! Но вы не могли бы заменить ((примените juxt (rest v)) m) с (map (partial get m) (rest v))? Или, так как встроенные реализации карты также можно вызывать, вы можете упростить все дальше - (map m (rest v)). Есть ли какая-то конкретная причина, по которой вы использовали juxt? –

0

более общий способ структурировать это один, было бы начать с исходной карты, а затем сократить набор функций преобразования над ним:

«Предположим, я хочу, чтобы положить сумму ключей: а и : b в ключ: d? "

user> (def m {:a 0 :b 0 :c 0 :d 0} 
#'user/m 
user> (reduce #(%2 %1) m [#(assoc % :a (inc (:a %))) 
          #(assoc % :b (inc (:b %))) 
          #(assoc % :d (+ (:a %) (:b %)))]) 
{:a 1, :c 0, :b 1, :d 2} 

Хотя это более многословным, потому что вы должны указать действие (assoc) и цель (:b и т.д.) для каждого из них, она позволяет выразить любое преобразование и дает вам место, чтобы поставить дополнительные аргументы. Возможно, что более важно, это делает упорядочение явным, не зависящим от внутреннего упорядочения карты.

Вы можете структурировать входные данные как [целевое действие] пар, чтобы сделать его немного менее многословным:

user> (reduce (fn [result [target action]] 
        (assoc result target (action result))) 
       m 
       [[:a #(inc (:a %))] 
        [:b #(inc (:b %))] 
        [:d #(+ (:a %) (:b %))]]) 
{:a 1, :c 0, :b 1, :d 2} 
+0

Интересный @Arthur Ufeldt. Есть ли способ уменьшить избыточность в первых двух анонимных функциональных элементах коллекции в этом 'reduce'? – stand

+0

Вы можете сделать это менее подробным, сделав его более конкретным, хотя это будет затруднять выражение действий, таких как «a - это больше, чем b». –