std.map

spork-lang 0.6.0

Persistent map traversal, nested updates, merging, selection, filtering, and transformation.

The std.map namespace provides persistent nested updates, key transformations, merging, selection, and inversion. Require it explicitly; these names are not installed into every namespace.

Usage: (ns my-file (:require [std.map :as m]))

m.keys / m.vals#

Return keys or values as persistent vectors. Their order follows the map's unspecified iteration order.

(def ks (m.keys {:a 1 :b 2 :c 3}))
(vector? ks)                      ; => true
(= (set ks) (set [:a :b :c]))    ; => true

(def vs (m.vals {:a 1 :b 2 :c 3}))
(vector? vs)                      ; => true
(= (set vs) (set [1 2 3]))       ; => true

(m.keys {})                       ; => []
(m.vals {})                       ; => []

m.entries#

Get key-value pairs as vector of vectors.

(into {} (m.entries {:a 1 :b 2})) ; => {:a 1 :b 2}
(m.entries {:x 10})               ; => [[:x 10]]

m.update#

Applies a function to the current value and associates the result. If the key is absent, the function receives nil.

(m.update {:a 1} :a inc)          ; => {:a 2}
(m.update {:a 1 :b 2} :b #(* % 10))  ; => {:a 1 :b 20}
(m.update {:count 5} :count dec)  ; => {:count 4}

m.update-with#

Like m.update, but supplies default when the key is absent. A stored nil remains nil and is not replaced by the default.

(m.update-with {:a 1} :a inc 0)   ; => {:a 2}
(m.update-with {:a 1} :b inc 0)   ; => {:a 1 :b 1}
(m.update-with {} :count inc 0)   ; => {:count 1}

m.get-in#

Traverses a sequence of keys through nested maps. It returns nil when a lookup fails; an empty key path returns the original map.

(m.get-in {:a {:b {:c 1}}} [:a :b :c])  ; => 1
(m.get-in {:a {:b 2}} [:a :b])          ; => 2
(m.get-in {:a 1} [:a])                  ; => 1
(m.get-in {:a {:b 2}} [:a :c])          ; => nil

m.get-in-or#

Get-in with a default value. A stored nil is treated the same as a missing path.

(m.get-in-or {:a {:b 1}} [:a :b] 42)   ; => 1
(m.get-in-or {:a {}} [:a :b] 42)       ; => 42
(m.get-in-or {} [:a :b :c] :missing)   ; => :missing

m.assoc-in#

Associates a value at a nonempty path, creating missing intermediate maps. Existing intermediate values must themselves support map lookup and association.

(m.assoc-in {:a {}} [:a :b] 1)         ; => {:a {:b 1}}
(m.assoc-in {} [:a :b :c] 42)          ; => {:a {:b {:c 42}}}
(m.assoc-in {:a {:b 1}} [:a :b] 99)    ; => {:a {:b 99}}
(m.assoc-in {:a {:b 1}} [:a :c] 2)     ; => {:a {:b 1 :c 2}}

m.update-in#

Updates a value at a nonempty path. Missing intermediate maps are created, and a missing final value is passed to the function as nil.

(m.update-in {:a {:b 1}} [:a :b] inc)  ; => {:a {:b 2}}
(m.update-in {:a {:b {:c 5}}} [:a :b :c] #(* % 10))
; => {:a {:b {:c 50}}}
(m.update-in {:stats {:count 0}} [:stats :count] inc)
; => {:stats {:count 1}}

m.select-keys#

Select only specified keys from map.

(m.select-keys {:a 1 :b 2 :c 3} [:a :c])  ; => {:a 1 :c 3}
(m.select-keys {:a 1 :b 2} [:a :b :c])    ; => {:a 1 :b 2}
(m.select-keys {:a 1 :b 2} [:x :y])       ; => {}
(m.select-keys {:a 1 :b 2} [])            ; => {}

m.dissoc-in#

Removes the final key at a nonempty nested path. The parent path should exist; when an intermediate key is missing, the current implementation associates nil at that point.

(m.dissoc-in {:a {:b 1 :c 2}} [:a :b])    ; => {:a {:c 2}}
(m.dissoc-in {:a {:b {:c 1}}} [:a :b :c]) ; => {:a {:b {}}}
(m.dissoc-in {:x {:y 1}} [:x :y])         ; => {:x {}}
(m.dissoc-in {} [:x :y])                   ; => {:x nil}

m.merge#

Merges zero or more maps into a new persistent map. Later values override earlier ones, and nil inputs are ignored.

(m.merge {:a 1} {:b 2})               ; => {:a 1 :b 2}
(m.merge {:a 1} {:a 2})               ; => {:a 2}
(m.merge {:a 1} {:b 2} {:c 3})        ; => {:a 1 :b 2 :c 3}
(m.merge {:a 1 :b 1} {:b 2} {:b 3})   ; => {:a 1 :b 3}
(m.merge {:a 1} nil {:b 2})           ; => {:a 1 :b 2}

m.merge-with#

Merge using function to combine values for duplicate keys.

(m.merge-with + {:a 1} {:a 2})        ; => {:a 3}
(m.merge-with + {:a 1 :b 2} {:a 3 :b 4})  ; => {:a 4 :b 6}
(m.merge-with into {:a [1]} {:a [2]})     ; => {:a [1 2]}
(m.merge-with into {:a #{1}} {:a #{2 3}}) ; => {:a #{1 2 3}}

m.rename-keys#

Renames keys according to a mapping. If several source keys resolve to the same target, one overwrites the others according to the source map's unspecified iteration order.

(m.rename-keys {:a 1 :b 2} {:a :x})        ; => {:x 1 :b 2}
(m.rename-keys {:a 1 :b 2} {:a :x :b :y})  ; => {:x 1 :y 2}
(m.rename-keys {:a 1 :b 2} {:c :z})        ; => {:a 1 :b 2}
(m.rename-keys {:old-name "value"} {:old-name :new-name})
; => {:new-name "value"}

m.invert#

Swaps keys and values. Values must be hashable; duplicate values collapse to one entry according to unspecified map iteration order.

(m.invert {:a 1 :b 2})            ; => {1 :a 2 :b}
(m.invert {:x "hello" :y "world"}) ; => {"hello" :x "world" :y}
(m.invert {1 :a 2 :b})            ; => {:a 1 :b 2}

m.map-keys / m.map-vals#

Transform keys or values. Results from m.map-keys must be hashable, and key collisions overwrite according to unspecified map iteration order.

; Map over keys
(m.map-keys (fn [k] k.name) {:a 1 :b 2}) ; => {"a" 1 "b" 2}
(m.map-keys str {1 :a 2 :b})      ; => {"1" :a "2" :b}
(m.map-keys inc {1 :a 2 :b})      ; => {2 :a 3 :b}

; Map over values
(m.map-vals inc {:a 1 :b 2})      ; => {:a 2 :b 3}
(m.map-vals str {:a 1 :b 2})      ; => {:a "1" :b "2"}
(m.map-vals count {:a [1 2] :b [1 2 3]})  ; => {:a 2 :b 3}

m.filter-keys / m.filter-vals#

Filter by predicate on keys or values.

; Filter by keys
(m.filter-keys #(isinstance % Keyword) {:a 1 "b" 2}) ; => {:a 1}
(m.filter-keys #(= :a %) {:a 1 :b 2})     ; => {:a 1}

; Filter by values
(m.filter-vals even? {:a 1 :b 2 :c 3 :d 4})  ; => {:b 2 :d 4}
(m.filter-vals pos? {:a -1 :b 0 :c 1 :d 2})  ; => {:c 1 :d 2}
(m.filter-vals #(not (= % nil)) {:a 1 :b nil :c 2}) ; => {:a 1 :c 2}

m.deep-merge#

Recursively merges values when both sides are persistent Spork maps. In every other conflict, the later value replaces the earlier one.

(m.deep-merge {:a {:b 1}} {:a {:c 2}})
; => {:a {:b 1 :c 2}}

(m.deep-merge {:a {:b {:c 1}}} {:a {:b {:d 2}}})
; => {:a {:b {:c 1 :d 2}}}

(m.deep-merge {:a {:x 1}} {:a {:x 2}})
; => {:a {:x 2}}

(m.deep-merge {:config {:debug false :port 8080}}
              {:config {:debug true}})
; => {:config {:debug true :port 8080}}