diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index b2f9f4c..57cf8c6 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -650,6 +650,16 @@ (transform* [this structure next-fn] (n/map-vals-transform structure next-fn))) +(defnav + ^{:doc "Navigate to each key of the map. This is more efficient than + navigating via [ALL FIRST]"} + MAP-KEYS + [] + (select* [this structure next-fn] + (doseqres NONE [k (keys structure)] + (next-fn k))) + (transform* [this structure next-fn] + (n/map-keys-transform structure next-fn))) (defcollector VAL [] diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index 30ce51f..3848391 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -210,8 +210,12 @@ -(defprotocol MapValsTransformProtocol - (map-vals-transform [structure next-fn])) +(defprotocol MapTransformProtocol + (map-vals-transform [structure next-fn]) + (map-keys-transform [structure next-fn]) + ) + + (defn map-vals-non-transient-transform [structure empty-map next-fn] (reduce-kv @@ -223,10 +227,22 @@ empty-map structure)) -(extend-protocol MapValsTransformProtocol +(defn map-keys-non-transient-transform [structure empty-map next-fn] + (reduce-kv + (fn [m k v] + (let [newk (next-fn k)] + (if (identical? newk i/NONE) + m + (assoc m newk v)))) + empty-map + structure)) + +(extend-protocol MapTransformProtocol nil (map-vals-transform [structure next-fn] nil) + (map-keys-transform [structure next-fn] + nil) #?(:clj clojure.lang.PersistentArrayMap) @@ -257,17 +273,48 @@ array )] (clojure.lang.PersistentArrayMap. array))))) - + #?(:clj + (map-keys-transform [structure next-fn] + (let [k-it (.keyIterator structure) + v-it (.valIterator structure) + none-cell (i/mutable-cell 0) + len (.count structure) + array (i/fast-object-array (* 2 len))] + (loop [i 0 + j 0] + (if (.hasNext k-it) + (let [k (.next k-it) + v (.next v-it) + newk (next-fn k)] + (if (identical? newk i/NONE) + (do + (i/update-cell! none-cell inc) + (recur (+ i 2) j)) + (do + (aset array j newk) + (aset array (inc j) v) + (recur (+ i 2) (+ j 2))))))) + (let [none-count (i/get-cell none-cell) + array (if (not= 0 none-count) + (java.util.Arrays/copyOf array (int (* 2 (- len none-count)))) + array + )] + (clojure.lang.PersistentArrayMap/createAsIfByAssoc array))))) #?(:cljs cljs.core/PersistentArrayMap) #?(:cljs (map-vals-transform [structure next-fn] (map-vals-non-transient-transform structure {} next-fn))) + #?(:cljs + (map-keys-transform [structure next-fn] + (map-keys-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 (empty structure) next-fn)) + (map-keys-transform [structure next-fn] + (map-keys-non-transient-transform structure (empty structure) next-fn)) #?(:clj clojure.lang.PersistentHashMap :cljs cljs.core/PersistentHashMap) @@ -283,7 +330,18 @@ #?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY)) structure))) + (map-keys-transform [structure next-fn] + (persistent! + (reduce-kv + (fn [m k v] + (let [newk (next-fn k)] + (if (identical? newk i/NONE) + m + (assoc! m newk v)))) + (transient + #?(:clj clojure.lang.PersistentHashMap/EMPTY :cljs cljs.core.PersistentHashMap.EMPTY)) + structure))) #?(:clj Object :cljs default) (map-vals-transform [structure next-fn] @@ -294,9 +352,17 @@ m (assoc m k newv)))) (empty structure) + structure)) + (map-keys-transform [structure next-fn] + (reduce-kv + (fn [m k v] + (let [newk (next-fn k)] + (if (identical? newk i/NONE) + m + (assoc m newk v)))) + (empty structure) structure))) - (defn srange-select [structure start end next-fn] (next-fn (if (string? structure) diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index ef36996..5cfc022 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -1455,3 +1455,17 @@ (is (predand= vector? [1 0] (setval s/AFTER-ELEM 0 v))) (is (predand= vector? [1 0] (setval (s/srange 1 1) [0] v))) )) + +(defspec map-keys-all-first-equivalence-transform + (for-all+ + [m (limit-size 10 (gen/map gen/int gen/keyword))] + (= (transform s/MAP-KEYS inc m) + (transform [s/ALL s/FIRST] inc m ) + ))) + +(defspec map-keys-all-first-equivalence-select + (for-all+ + [m (limit-size 10 (gen/map gen/int gen/keyword))] + (= (select s/MAP-KEYS m) + (select [s/ALL s/FIRST] m) + )))