更快/更惯用的方式来实现"地图 - 何时"?

时间:2017-02-21 11:57:17

标签: performance clojure

我想实现一个映射一系列映射的函数,并在谓词匹配时更新值

这是第一份工作草案:

(defn update-if
  ([m k pred f]
   (let [init (get m k)]
     (if (and (not-nil? init) (pred init))
           (update m k f)
           m)))
  ([m bindings]
   (reduce-kv
     (fn [agg k v]
       (let [[pred f] v]
         (update-if agg k pred f)))
    m bindings)))

(update-if {:a 1 :b 2} {:a [even? inc] :b [even? dec]}) ;; ==> {:a 1 :b 1}
(update-if {:a 1 :b 2} :b even? dec) ;; ==> {:a 1 :b 1}

(defn map-when
  "Walks a collection of associative collections
  and applies functions based on predicates
  Output :
  (map-when {:a [even? inc] :b [nan? zero]} '({:a 1 :b NaN} {:a 2 :b 7} {:a 4 :b NaN}))
  =
  ({:a 1 :b 0} {:a 3 :b 7} {:a 5 :b 0})"
  ([bindings data]
   (reduce
     (fn [acc row]
       (conj acc (update-if row bindings)))
    '() data))
  ([pred f data]
   (map
     (fn [x]
       (if (and (not-nil? x) (pred x))
             (f x)
             x))
    data)))

不比零?检查很重要(这里),因为它只是意味着数据丢失。 该功能需要大约2秒才能在100万个随机{:a :b}地图(包括随机数据)上执行此操作。

我觉得很奇怪,在核心/核心相关的库中没有这个功能。 是否有一些性能提示可以改善这一点?我尝试过瞬态,但它不适用于空列表'()

由于

2 个答案:

答案 0 :(得分:2)

你应该look at the specter library。它可能有你想要的东西。例如:

(def data {:a [{:aa 1 :bb 2}
               {:cc 3}]
           :b [{:dd 4}]})

;; Manual Clojure
(defn map-vals [m afn]
  (->> m (map (fn [[k v]] [k (afn v)])) (into {})))

(map-vals data
  (fn [v]
    (mapv
      (fn [m]
        (map-vals
          m
          (fn [v] (if (even? v) (inc v) v))))
      v)))

;; Specter
(transform [MAP-VALS ALL MAP-VALS even?] inc data)

答案 1 :(得分:1)

生成必要的lambda以最大化可重用性。

(defn cond-update-fn [clauses]
  (fn [m] 
    (reduce (fn [m [k [pred f]]] 
               (cond-> m
                  (and (contains? m k)
                       (pred (get m k))) (update k f)))
      m 
      clauses)))

如果你的preds和fns在编译时已知,那么编写一个宏(左边作为读者的练习)会因为没有预迭代开销而提供更高的性能。

在任何情况下重复使用:

(def input [{:a 42, :b 42} {:a 42,:b 43}])

(def cond-update 
  (cond-update-fn {:a [even? inc]
                   :b [odd? dec]}))

(map cond-update input)

;-> ({:a 43, :b 42} {:a 43, :b 42})

;; Transducer
(into [] (map cond-update) input)

;-> [{:a 43, :b 42} {:a 43, :b 42}]

;; Standalone

(cond-update {:a 32})
;-> {:a 33}