diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 6f3ac43..e93357e 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -106,6 +106,7 @@ (reduce-kv (fn [m k v] (assoc m k (inc v))) {} data) (manual-similar-reduce-kv data) (transform [ALL LAST] inc data) + (transform MAP-VALS inc data) )) (let [data (->> (for [i (range 1000)] [i i]) (into {}))] @@ -114,6 +115,7 @@ (reduce-kv (fn [m k v] (assoc m k (inc v))) {} data) (manual-similar-reduce-kv data) (transform [ALL LAST] inc data) + (transform MAP-VALS inc data) )) diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index 756d127..9d7bac2 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -163,6 +163,14 @@ ALL (comp-paths (i/->AllNavigator))) +(defnav MAP-VALS [] + (select* [this structure next-fn] + (doall (mapcat next-fn (vals structure)))) + (transform* [this structure next-fn] + (i/map-vals-transform structure next-fn) + )) + + (def VAL (i/->ValCollect)) (def diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 196a29d..c1db2ed 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -702,6 +702,49 @@ (transform* [this structure next-fn] (all-transform structure next-fn))) +(defprotocol MapValsTransformProtocol + (map-vals-transform [structure next-fn])) + +(defn map-vals-non-transient-transform [structure empty-map next-fn] + (reduce-kv + (fn [m k v] + (assoc m k (next-fn v))) + empty-map + structure)) + +(extend-protocol MapValsTransformProtocol + #+clj clojure.lang.PersistentArrayMap #+cljs cljs.core/PersistentArrayMap + (map-vals-transform [structure next-fn] + (map-vals-non-transient-transform structure {} next-fn) + ) + + #+clj clojure.lang.PersistentTreeMap #+cljs cljs.core/PersistentTreeMap + (map-vals-transform [structure next-fn] + (map-vals-non-transient-transform structure (sorted-map) next-fn) + ) + + #+clj clojure.lang.PersistentHashMap #+cljs cljs.core/PersistentHashMap + (map-vals-transform [structure next-fn] + (persistent! + (reduce-kv + (fn [m k v] + (assoc! m k (next-fn v))) + (transient + #+clj clojure.lang.PersistentHashMap/EMPTY #+cljs cljs.core.PersistentHashMap.EMPTY + ) + structure + ))) + + #+clj Object #+cljs default + (map-vals-transform [structure next-fn] + (reduce-kv + (fn [m k v] + (assoc m k (next-fn v))) + (empty structure) + structure)) + ) + + (deftype ValCollect []) (extend-protocol p/Collector diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index cf0fbb1..64acbc4 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -64,8 +64,9 @@ (defspec select-all-on-map (for-all+ - [m (limit-size 5 (gen/map gen/keyword gen/int))] - (= (select [s/ALL s/LAST] m) + [m (limit-size 5 (gen/map gen/keyword gen/int)) + p (gen/elements [s/MAP-VALS [s/ALL s/LAST]])] + (= (select p m) (for [[k v] m] v)) )) @@ -81,8 +82,9 @@ (defspec transform-all-on-map (for-all+ - [m (limit-size 5 (gen/map gen/keyword gen/int))] - (= (transform [s/ALL s/LAST] inc m) + [m (limit-size 5 (gen/map gen/keyword gen/int)) + p (gen/elements [s/MAP-VALS [s/ALL s/LAST]])] + (= (transform p inc m) (into {} (for [[k v] m] [k (inc v)])) )))