1    Introduction

1.1    Overview

std.lib.collection provides extended collection utilities beyond clojure.core, including:

  • Advanced map operations (map-keys, map-vals, filter-keys, filter-vals)
  • Nested data structure manipulation (merge-nested, flatten-nested)
  • Sequence utilities (queue, seqify, unlazy)
  • Tree operations (tree-flatten, tree-nestify)
  • Data diffing and patching (diff, diff:patch, diff:unpatch)

2    Walkthrough

2.1    Working with maps

Most day-to-day use starts with map transformations. map-keys and map-vals apply a function to every key or value, while filter-keys and filter-vals keep entries that satisfy a predicate.

transform map keys and values

(map-keys inc {0 :a 1 :b 2 :c})
=> {1 :a 2 :b 3 :c}

(map-vals inc {:a 1 :b 2 :c 3})
=> {:a 2 :b 3 :c 4}

filter entries by key or value

(filter-keys even? {0 :a 1 :b 2 :c})
=> {0 :a 2 :c}

(filter-vals even? {:a 1 :b 2 :c 3})
=> {:b 2}

rename and qualify keys

(rename-keys {:a 1 :b 2} {:a :name :b :id})
=> {:name 1 :id 2}

(qualify-keys {:a 1 :b 2} :user)
=> #:user{:a 1 :b 2}

(unqualify-keys {:user/a 1 :user/b 2})
=> {:a 1 :b 2}

2.2    Nested maps

Configuration data is often deeply nested. merge-nested recurses into maps, assoc-new only adds missing keys, and dissoc-nested removes a path and collapses empty parents.

merge nested configurations

(merge-nested {:server {:host "localhost" :port 8080}}
              {:server {:port 3000}})
=> {:server {:host "localhost" :port 3000}}

(merge-nested-new {:server {:host "localhost"}}
                  {:server {:port 3000}})
=> {:server {:host "localhost" :port 3000}}

update a nested path safely

(dissoc-nested {:a {:b {:c 1 :d 2}}}
               [:a :b :c])
=> {:a {:b {:d 2}}}

2.3    Tree-shaped data

Flat maps with slash-separated keys can be nested into trees with tree-nestify, and deep trees can be flattened back with tree-flatten.

flatten and nest tree keys

(tree-flatten {:a {:b {:c 1 :d 2}
                   :e {:f 3}}})
=> {:a/b/c 1 :a/b/d 2 :a/e/f 3}

(tree-nestify {:a/b/c 1 :a/b/d 2})
=> {:a {:b {:c 1 :d 2}}}

2.4    Diffing and patching

diff compares two maps and returns additions (:+), removals (:-), and changes (:> and :<). The resulting patch can be applied or reversed.

compute and apply a diff

(diff {:a 2} {:a 1})
=> {:+ {} :- {} :> {[:a] 2}}

(let [old {:a {:b 1 :d 3}}
      new {:a {:c 2 :d 4}}
      d   (diff new old true)]
  (diff:patch old d))
=> {:a {:c 2 :d 4}}

(let [old {:a {:b 1 :d 3}}
      new {:a {:c 2 :d 4}}
      d   (diff new old true)]
  (diff:unpatch new d))
=> {:a {:b 1 :d 3}}

2.5    Sequences and queues

queue builds a persistent queue, seqify normalises values to sequences, and unlazy forces lazy sequences.

use a persistent queue

(-> (queue 1 2 3 4)
    pop
    vec)
=> [2 3 4]

normalise values to sequences

(seqify 1)
=> [1]

(seqify [1 2])
=> [1 2]

(unlazy (map inc [1 2 3]))
=> [2 3 4]

2.6    End-to-end: reshaping config data

Combining the utilities above makes it easy to reshape data. Here a flat keystore is transformed into a nested login record using find-templates and transform.

transform a keystore into a nested record

(transform {:keystore {:hash  "{{hash}}"
                       :salt  "{{salt}}"
                       :email "{{email}}"}
            :db       {:login {:user {:hash "{{hash}}"
                                      :salt "{{salt}}"}
                               :value "{{email}}"}}}
           [:keystore :db]
           {:hash "1234"
            :salt "ABCD"
            :email "[email protected]"})
=> {:login {:user {:hash "1234"
                   :salt "ABCD"}
            :value "[email protected]"}}

3    Type Predicates



cons? ^

[x]
Added 3.0

checks if object is instance of `clojure.lang.Cons`

v 3.0
(defn cons?
  ([x] (or (instance? clojure.lang.Cons x)
           (instance? clojure.lang.ChunkedCons x))))
link
(cons? (cons 1 [1 2 3])) => true

form? ^

[form]
Added 3.0

checks if object is a lisp form

v 3.0
(defn form?
  ([form]
   (or (cons? form)
       (list? form)
       (lazy-seq? form)
       (or (instance? clojure.lang.PersistentVector$ChunkedSeq form)))))
link
(form? '(+ 1 2)) => true (form? [1 2]) => false (form? {:a 1}) => false (form? "a") => false

hash-map? ^

[x]
Added 3.0

returns `true` if `x` implements `clojure.lang.APersistentMap`.

v 3.0
(defn hash-map?
  ([x] (instance? clojure.lang.APersistentMap x)))
link
(hash-map? {}) => true (hash-map? []) => false

lazy-seq? ^

[x]
Added 3.0

returns `true` if `x` implements `clojure.lang.LazySeq`.

v 3.0
(defn lazy-seq?
  ([x] (instance? clojure.lang.LazySeq x)))
link
(lazy-seq? (map inc [1 2 3])) => true (lazy-seq? ()) => false

4    Sequence Utilities



queue ^

[] [x] [x & xs]
Added 3.0

returns a `clojure.lang.PersistentQueue` object.

v 3.0
(defn queue
  ([] (clojure.lang.PersistentQueue/EMPTY))
  ([x] (conj (queue) x))
  ([x & xs] (apply conj (queue) x xs)))
link
(pop (queue 1 2 3 4)) => [2 3 4]

seqify ^

[x]
Added 3.0

if not a sequence, then make one

v 3.0
(defn seqify
  ([x]
   (if (or (coll? x)
           (instance? java.util.List x))
     x
     [x])))
link
(seqify 1) => [1] (seqify [1]) => [1]

unlazy ^

[x]
Added 3.0

works on both lazy seqs and objects

v 3.0
(defn unlazy
  ([x]
   (if (lazy-seq? x)
     (doall x)
     x)))
link
(unlazy (map inc [1 2 3])) => [2 3 4] (unlazy [1 2 3]) => [1 2 3]

unseqify ^

[x]
Added 3.0

if a sequence, takes first element

v 3.0
(defn unseqify
  ([x]
   (if (coll? x)
     (first x)
     x)))
link
(unseqify [1]) => 1 (unseqify 1) => 1

5    Map Operations



map-entries ^

[f m]
Added 3.0

manipulates a map given the function

v 3.0
(defn map-entries
  ([f m]
   (->> (map f m)
        (into {}))))
link
(map-entries (fn [[k v]] [(keyword (str v)) (name k)]) {:a 1 :b 2 :c 3}) => {:1 "a", :2 "b", :3 "c"}

map-juxt ^

[[kf vf] arr]
Added 3.0

creates a map from sequence with key and val functions

v 3.0
(defn map-juxt
  ([[kf vf] arr]
   (->> (map (juxt kf vf) arr)
        (into {}))))
link
(map-juxt [str inc] [1 2 3 4 5]) => {"1" 2, "2" 3, "3" 4, "4" 5, "5" 6}

map-keys ^

[f m]
Added 3.0

changes the keys of a map

v 3.0
(defn map-keys
  ([f m]
   (reduce (fn [out [k v]]
             (assoc out (f k) v))
           {}
           m)))
link
(map-keys inc {0 :a 1 :b 2 :c}) => {1 :a, 2 :b, 3 :c}

map-vals ^

[f m]
Added 3.0

changes the values of a map

v 3.0
(defn map-vals
  ([f m]
   (reduce (fn [out [k v]]
             (assoc out k (f v)))
           {}
           m)))
link
(map-vals inc {:a 1 :b 2 :c 3}) => {:a 2, :b 3, :c 4}

pmap-entries ^

[f m]
Added 3.0

uses pmap across the entries

v 3.0
(defn pmap-entries
  ([f m]
   (->> (pmap f m)
        (into {}))))
link
(pmap-entries (fn [[k v]] [(keyword (str v)) (name k)]) {:a 1}) => {:1 "a"}

pmap-vals ^

[f m]
Added 3.0

uses pmap across the map values

v 3.0
(defn pmap-vals
  ([f m]
   (->> (pmap (fn [[k v]]
                [k (f v)])
              m)
        (into {}))))
link
(pmap-vals inc {:a 1 :b 2}) => {:a 2 :b 3}

6    Map Filtering



filter-keys ^

[pred m]
Added 3.0

filters map based upon map keys

v 3.0
(defn filter-keys
  ([pred m]
   (reduce (fn [out [k v]]
             (if (pred k)
               (assoc out k v)
               out))
           {}
           m)))
link
(filter-keys even? {0 :a 1 :b 2 :c}) => {0 :a, 2 :c}

filter-vals ^

[pred m]
Added 3.0

filters map based upon map values

v 3.0
(defn filter-vals
  ([pred m]
   (reduce (fn [out [k v]]
             (if (pred v)
               (assoc out k v)
               out))
           {}
           m)))
link
(filter-vals even? {:a 1 :b 2 :c 3}) => {:b 2}

keep-vals ^

[f m] [f pred m]
Added 3.0

filters map based upon map values

v 3.0
(defn keep-vals
  ([f m]
   (reduce (fn [out [k v]]
             (if-let [new (f v)]
               (assoc out k new)
               out))
           {}
           m))
  ([f pred m]
   (reduce (fn [out [k v]]
             (let [new (f v)]
               (if (pred new)
                 (assoc out k new)
                 out)))
           {}
           m)))
link
(keep-vals even? {:a 1 :b 2 :c 3}) => {:b true}

7    Key Operations



qualified-keys ^

[m] [m ns]
Added 3.0

takes only the namespaced keys of a map

v 3.0
(defn qualified-keys
  ([m]
   (filter-keys (fn [k] (and (keyword? k)
                             (namespace k)))
                m))
  ([m ns] 
   (let [pred (set (map h/strn (seqify ns)))]
     (filter-keys (fn [k] (and (keyword? k)
                               (pred (namespace k))))
                  m))))
link
(qualified-keys {:a 1 :ns/b 1}) => #:ns{:b 1} (qualified-keys {:a 1 :ns.1/b 1 :ns.2/c 1} :ns.1) => #:ns.1{:b 1}

qualify-keys ^

[m ns]
Added 3.0

lifts all unqualified keys

v 3.0
(defn qualify-keys
  ([m ns]
   (let [ns (h/strn ns)]
     (map-keys (fn [k]
                 (if (and (keyword? k)
                          (not (namespace k)))
                   (keyword ns (name k))
                   k))
               m))))
link
(qualify-keys {:a 1} :ns.1) => #:ns.1{:a 1} (qualify-keys {:a 1 :ns.2/c 1} :ns.1) => {:ns.1/a 1, :ns.2/c 1}

rename-keys ^

[m trs]
Added 4.0

rename keys in map

v 4.0
(defn rename-keys
  ([m trs]
   (reduce (fn [m [from to]]
             (if (contains? m from)
               (assoc (dissoc m from)
                      to (get m from))
               m))
           m
           trs)))
link
(rename-keys {:a 1} {:a :b}) => {:b 1}

unqualified-keys ^

[m]
Added 3.0

takes only the namespaced keys of a map

v 3.0
(defn unqualified-keys
  ([m]
   (filter-keys (fn [k] (and (keyword? k)
                             (nil? (namespace k))))
                m)))
link
(unqualified-keys {:a 1 :ns/b 1}) => {:a 1}

unqualify-keys ^

[m] [m ns]
Added 3.0

unqualifies keys in the map

v 3.0
(defn unqualify-keys
  ([m]
   (map-keys (fn [k] (keyword (name k))) m))
  ([m ns]
   (let [ns (h/strn ns)]
     (map-keys (fn [k]
                 (if (and (keyword? k)
                          (= (namespace k) ns))
                   (keyword (name k))
                   k))
               m))))
link
(unqualify-keys {:a 1 :ns.1/b 1 :ns.2/c 1}) => {:a 1, :b 1, :c 1} (unqualify-keys {:a 1 :ns.1/b 1 :ns.2/c 1} :ns.1) => {:a 1, :b 1, :ns.2/c 1}

8    Nested Operations



assoc-new ^

[m k v] [m k v & more]
Added 3.0

only assoc if the value in the original map is nil

v 3.0
(defn assoc-new
  ([m k v]
   (if (contains? m k) m (assoc m k v)))
  ([m k v & more]
   (apply assoc-new (assoc-new m k v) more)))
link
(assoc-new {:a 1} :b 2) => {:a 1 :b 2} (assoc-new {:a 1} :a 2 :b 2) => {:a 1 :b 2}

dissoc-nested ^

[m [k & ks]]
Added 3.0

dissocs recursively into a map removing empty entries

v 3.0
(defn dissoc-nested
  ([m [k & ks]]
   (if-not ks
     (dissoc m k)
     (let [nm (dissoc-nested ((or m {}) k) ks)]
       (cond (empty? nm) (dissoc m k)
             :else (assoc m k nm))))))
link
(dissoc-nested {:a {:b {:c 1}}} [:a :b :c]) => {}

flatten-nested ^

[x]
Added 3.0

flattens all elements the collection

v 3.0
(defn flatten-nested
  ([x]
   (filter (complement coll?)
           (rest (tree-seq coll? seq x)))))
link
(flatten-nested [1 2 #{3 {4 5}}]) => [1 2 3 4 5]

merge-nested ^

[& maps]
Added 3.0

merges nested values from left to right.

v 3.0
(defn merge-nested
  ([& maps]
   (apply merge-with (fn [& args]
                       (if (every? #(or (map? %) (nil? %)) args)
                         (apply merge-nested args)
                         (last args)))
          maps)))
link
(merge-nested {:a {:b {:c 3}}} {:a {:b 3}}) => {:a {:b 3}} (merge-nested {:a {:b {:c 1 :d 2}}} {:a {:b {:c 3}}}) => {:a {:b {:c 3 :d 2}}}

merge-nested-new ^

[& maps]
Added 3.0

merges nested values from left to right, provided the merged value does not exist

v 3.0
(defn merge-nested-new
  ([& maps]
   (apply merge-nested (reverse maps))))
link
(merge-nested-new {:a {:b 2}} {:a {:c 2}}) => {:a {:b 2 :c 2}} (merge-nested-new {:b {:c :old}} {:b {:c :new}}) => {:b {:c :old}}

9    Tree Operations



find-templates ^

[m] [m path saved]
Added 3.0

finds the template with associated path

v 3.0
(defn find-templates
  ([m]
   (find-templates m [] {}))
  ([m path saved]
   (let [template? (fn [^String s]
                     (and (string? s)
                          (.startsWith s "{{")
                          (.endsWith s "}}")))]
     (reduce-kv (fn [out k v]
                  (cond (template? v)
                        (assoc out v (conj path k))

                        (map? v)
                        (find-templates v (conj path k) out)

                        :else
                        out))
                saved
                m))))
link
(find-templates {:hash "{{hash}}" :salt "{{salt}}" :email "{{email}}" :user {:firstname "{{firstname}}" :lastname "{{lastname}}"}}) => {"{{hash}}" [:hash] "{{salt}}" [:salt] "{{email}}" [:email] "{{firstname}}" [:user :firstname] "{{lastname}}" [:user :lastname]}

reshape ^

[m rels]
Added 3.0

moves values around in a map according to a table

v 3.0
(defn reshape
  ([m rels]
   (reduce (fn [out [to from]]
             (if (= to from)
               out
               (let [v (get-in m from)]
                 (-> out
                     (dissoc-nested from)
                     (assoc-in to v)))))
           m
           rels)))
link
(reshape {:a 1 :b 2} {[:c :d] [:a]}) => {:b 2, :c {:d 1}}

transform ^

[schema [from to] data]
Added 3.0

creates a transformation function

v 3.0
(defn transform
  ([schema [from to] data]
   (let [f (transform-fn* schema [from to])]
     (f data))))
link
(transform {:keystore {:hash "{{hash}}" :salt "{{salt}}" :email "{{email}}"} :db {:login {:user {:hash "{{hash}}" :salt "{{salt}}"} :value "{{email}}"}}} [:keystore :db] {:hash "1234" :salt "ABCD" :email "[email protected]"}) => {:login {:user {:hash "1234", :salt "ABCD"}, :value "[email protected]"}}

tree-flatten ^

[m] [m sep] [m sep path]
Added 3.0

flattens the entire map tree

v 3.0
(defn tree-flatten
  ([m]
   (tree-flatten m h/*sep*))
  ([m sep]
   (tree-flatten m sep []))
  ([m sep path]
   (->> (keep (fn [[k v]]
                (if (and (map? v) (not-empty v))
                  (tree-flatten v sep (conj path k))
                  (if (or (not (coll? v))
                          (not-empty v))
                    [(->> (conj path k)
                          (map h/strn)
                          (clojure.string/join sep)
                          keyword) v])))
              m)
        (into {}))))
link
(->> (tree-flatten {"a" {"b" {"c" 3 "d" 4} "e" {"f" 5 "g" 6}} "h" {"i" {}}}) (map-keys h/strn)) => {"a/b/c" 3, "a/b/d" 4, "a/e/f" 5, "a/e/g" 6}

tree-nestify ^

[m] [m sep] [m sep f]
Added 3.0

nests keys in the map

v 3.0
(defn tree-nestify
  ([m]
   (tree-nestify m h/*sep*))
  ([m sep]
   (tree-nestify m sep identity))
  ([m sep f]
   (let [pattern (re-create sep)]
     (reduce-kv (fn [m k v]
                  (let [path (->> (clojure.string/split (h/strn k) pattern)
                                  (map keyword))
                        v  (f v)]
                    (update-in m path
                               (fnil (fn [p]
                                       (cond (and (map? p) (map? v))
                                             (merge p v)
                                             :else v))
                                     v))))
                {}
                m))))
link
(tree-nestify {:a/b 2 :a/c 3}) => {:a {:b 2 :c 3}} (tree-nestify {:a/b {:e/f 1} :a/c {:g/h 1}}) => {:a {:b {:e/f 1} :c {:g/h 1}}}

tree-nestify:all ^

[m] [m sep]
Added 3.0

nests keys in the map and all submaps

v 3.0
(defn tree-nestify:all
  ([m]
   (tree-nestify:all m h/*sep*))
  ([m sep]
   (tree-nestify m sep
                 (fn [v] (if (hash-map? v)
                           (tree-nestify:all v sep)
                           v)))))
link
(tree-nestify:all {:a/b 2 :a/c 3}) => {:a {:b 2 :c 3}} (tree-nestify:all {:a/b {:e/f 1} :a/c {:g/h 1}}) => {:a {:b {:e {:f 1}} :c {:g {:h 1}}}}

10    Diff Operations



diff ^

[m1 m2] [m1 m2 reversible]
Added 3.0

finds the difference between two maps

v 3.0
(defn diff
  ([m1 m2] (diff m1 m2 false))
  ([m1 m2 reversible]
   (let [diff (hash-map :+ (diff:new m1 m2)
                        :- (diff:new m2 m1)
                        :> (diff:changes m1 m2))]
     (if reversible
       (assoc diff :< (diff:changes m2 m1))
       diff))))
link
(diff {:a 2} {:a 1}) => {:+ {} :- {} :> {[:a] 2}} (diff {:a {:b 1 :d 3}} {:a {:c 2 :d 4}} true) => {:+ {[:a :b] 1} :- {[:a :c] 2} :> {[:a :d] 3} :< {[:a :d] 4}}

diff:changed ^

[new old]
Added 3.0

outputs what has changed between the two maps

v 3.0
(defn diff:changed
  ([new old]
   (->> (diff new old)
        ((juxt :> :+))
        (apply merge)
        (reduce-kv (fn [out ks v]
                     (assoc-in out ks v))
                   {}))))
link
(diff:changed {:a {:b {:c 3 :d 4}}} {:a {:b {:c 3}}}) => {:a {:b {:d 4}}}

diff:changes ^

[m1 m2] [m1 m2 arr equal-fn]
Added 3.0

finds changes in nested maps, does not consider new elements

v 3.0
(defn diff:changes
  ([m1 m2]
   (diff:changes m1 m2 [] =))
  ([m1 m2 arr equal-fn]
   (reduce-kv (fn [out k1 v1]
                (if (contains? m2 k1)
                  (let [v2 (get m2 k1)]
                    (cond (and (hash-map? v1) (hash-map? v2))
                          (merge out (diff:changes v1 v2 (conj arr k1) equal-fn))

                          (equal-fn v1 v2)
                          out

                          :else
                          (assoc out (conj arr k1) v1)))
                  out))
              {}
              m1)))
link
(diff:changes {:a 2} {:a 1}) => {[:a] 2} (diff:changes {:a {:b 1 :c 2}} {:a {:b 1 :c 3}}) => {[:a :c] 2} (diff:changes {:a 1 :b 2 :c 3} {:a 1 :b 2}) => {} (diff:changes {:a 1 :b 2} {:a 1 :b 2 :c 3}) => {} (diff:changes {:a 1} {:a nil}) => {[:a] 1} (diff:changes {:a nil} {:a 1}) => {[:a] nil} (diff:changes {:a true} {:a false}) => {[:a] true} (diff:changes {:a false} {:a true}) => {[:a] false}

diff:new ^

[m1 m2] [m1 m2 arr]
Added 3.0

finds new elements in nested maps, does not consider changes

v 3.0
(defn diff:new
  ([m1 m2]
   (diff:new m1 m2 []))
  ([m1 m2 arr]
   (reduce-kv (fn [out k1 v1]
                (let [v2 (get m2 k1)]
                  (cond (and (hash-map? v1) (hash-map? v2))
                        (merge out (diff:new v1 v2 (conj arr k1)))

                        (not (contains? m2 k1))
                        (assoc out (conj arr k1) v1)

                        :else out)))
              {}
              m1)))
link
(diff:new {:a 2} {:a 1}) => {} (diff:new {:a {:b 1}} {:a {:c 2}}) => {[:a :b] 1} (diff:new {:a {:b 1 :c 2}} {:a {:b 1 :c 3}}) => {} (diff:new {:a 1 :b 2 :c 3} {:a 1 :b 2}) => {[:c] 3} (diff:new {:a 1 :b 2} {:a 1 :b 2 :c 3}) => {}

diff:patch ^

[m diff]
Added 3.0

patch from old to new

v 3.0
(defn diff:patch
  ([m diff]
   (->> m
        (#(reduce-kv (fn [m arr v]
                       (update-in m arr merge-or-replace v))
                     %
                     (merge (:+ diff) (:> diff))))
        (#(reduce (fn [m arr]
                    (dissoc-nested m arr))
                  %
                  (keys (:- diff)))))))
link
(let [m1 {:a {:b 1 :d 3}} m2 {:a {:c 2 :d 4}} df (diff m2 m1)] (diff:patch m1 df)) => {:a {:c 2 :d 4}}

diff:unpatch ^

[m diff]
Added 3.0

unpatch from new to old

v 3.0
(defn diff:unpatch
  ([m diff]
   (->> m
        (#(reduce-kv (fn [m arr v]
                       (update-in m arr merge-or-replace v))
                     %
                     (merge (:- diff) (:< diff))))
        (#(reduce (fn [m arr]
                    (dissoc-nested m arr))
                  %
                  (keys (:+ diff)))))))
link
(let [m1 {:a {:b 1 :d 3}} m2 {:a {:c 2 :d 4}} df (diff m2 m1 true)] (diff:unpatch m2 df)) => {:a {:b 1 :d 3}}

11    Collection Manipulation



deduped? ^

[coll]
Added 3.0

checks if elements in the collection are unique

v 3.0
(defn deduped?
  [coll]
  (= (count (set coll))
     (count coll)))
link
(deduped? [1 2 3 4]) => true (deduped? [1 2 1 4]) => false

element-at ^

[pred coll]
Added 3.0

finds the element within an array

v 3.0
(defn element-at
  ([pred coll]
   (loop [[x & more :as coll] coll]
     (cond (empty? coll) nil

           (pred x) x

           :else
           (recur more)))))
link
(element-at keyword? [1 2 :hello 4]) => :hello

index-at ^

[pred coll]
Added 3.0

finds the index of the first matching element in an array

v 3.0
(defn index-at
  ([pred coll]
   (loop [[x & more :as coll] coll
          i 0]
     (cond (empty? coll) -1

           (pred x) i

           :else
           (recur more (inc i))))))
link
(index-at even? [1 2 3 4]) => 1 (index-at keyword? [1 2 :hello 4]) => 2

insert-at ^

[coll i new] [coll i new & more]
Added 3.0

insert one or more elements at the given index

v 3.0
(defn insert-at
  ([coll i new]
   (apply conj (subvec coll 0 i) new (subvec coll i)))
  ([coll i new & more]
   (apply conj (subvec coll 0 i) new (concat more (subvec coll i)))))
link
(insert-at [:a :b] 1 :b :c) => [:a :b :c :b]

remove-at ^

[coll i] [coll i n]
Added 3.0

removes element at the specified index

v 3.0
(defn remove-at
  ([coll i]
   (remove-at coll i 1))
  ([coll i n]
   (cond (vector? coll)
         (reduce conj
                 (subvec coll 0 i)
                 (subvec coll (+ i n) (count coll)))

         :else
         (keep-indexed #(if (not (and (>= %1 i)
                                      (< %1 (+ i n)))) %2) coll))))
link
(remove-at [:a :b :c :d] 2) => [:a :b :d]

split-by ^

[pred coll]
Added 4.0

splits a sequences using a predicate

v 4.0
(defn split-by
  [pred coll]
  (loop [remaining coll
         current-chunk []
         result []]
    (if (empty? remaining)
      (if (empty? current-chunk)
        result
        (conj result current-chunk))
      (let [item (first remaining)]
        (if (pred item)
          (recur (rest remaining)
                 []
                 (conj result current-chunk))
          (recur (rest remaining)
                 (conj current-chunk item)
                 result))))))
link
(split-by #(= % :split) [:a :b :split :c :d :split :e]) => [[:a :b] [:c :d] [:e]] (split-by even? [1 3 5 2 7 9 4 11]) => [[1 3 5] [7 9] [11]] (split-by #(= % :split) [:split :a :b :split :c]) => [[] [:a :b] [:c]] (split-by #(= % :split) [:a :b :c]) => [[:a :b :c]]

transpose ^

[m]
Added 3.0

sets the vals and keys and vice-versa

v 3.0
(defn transpose
  ([m]
   (reduce (fn [out [k v]]
             (assoc out v k))
           {}
           m)))
link
(transpose {:a 1 :b 2 :c 3}) => {1 :a, 2 :b, 3 :c}

unfold ^

[f coll]
Added 3.0

unfolds using a generated function

v 3.0
(defn unfold
  ([f coll]
   (->> coll
        (list nil)
        (iterate (comp f second))
        rest
        (take-while some?)
        (map first))))
link
(unfold (fn [[i :as seed]] (if i (if-not (neg? i) [(* i 2) [(dec i)]]))) [10]) => [20 18 16 14 12 10 8 6 4 2 0]

12    std.lib.collection: A Comprehensive Summary

The std.lib.collection namespace provides a rich set of utility functions for working with Clojure's core data structures (maps, sequences, vectors, sets). It extends the functionality of clojure.core with specialized operations for nested data manipulation, key/value transformations, sequence processing, and advanced map differencing and patching. This module aims to simplify common collection-related tasks and provide more powerful tools for data transformation.

12.1    Core Concepts:

  • Collection Predicates: Functions to check the type of various collections (e.g., hash-map?, lazy-seq?, cons?, form?).n Sequence Utilities: Functions for manipulating sequences, including seqify, unseqify, unlazy, insert-at, remove-at, deduped?, and unfold.n Map Transformations: Extensive functions for transforming map keys, values, and entries, including map-keys, map-vals, map-juxt, map-entries, pmap-vals, pmap-entries, rename-keys, filter-keys, filter-vals, keep-vals, transpose.n Namespaced Key Handling: Utilities for working with namespaced keywords in maps, such as qualified-keys, unqualified-keys, qualify-keys, unqualify-keys.n Nested Map Operations: Powerful functions for merging, associating, and disassociating values in nested maps, including merge-nested, merge-nested-new, dissoc-nested, flatten-nested, tree-flatten, tree-nestify, tree-nestify:all.n Map Differencing and Patching: A sophisticated set of functions (diff, diff:changes, diff:new, diff:changed, diff:patch, diff:unpatch) for comparing two maps, identifying differences, and applying/reverting patches.n Data Transformation Pipelines: Functions like reshape and transform (with find-templates) for defining and applying complex data transformations based on schemas or templates.

12.2    Key Functions:

  • hash-map?, lazy-seq?, cons?, form?: Predicates for checking collection types.n queue: Creates a clojure.lang.PersistentQueue.n seqify, unseqify: Convert non-sequences to sequences and vice-versa.n unlazy: Forces evaluation of a lazy sequence.n map-keys, map-vals, map-juxt, map-entries: Apply functions to map keys, values, or entries. pmap-vals and pmap-entries provide parallel versions.n rename-keys: Renames keys in a map based on a mapping.n filter-keys, filter-vals, keep-vals: Filter map entries based on keys or values.n qualified-keys, unqualified-keys, qualify-keys, unqualify-keys: Manipulate namespaced keywords in maps.n assoc-new: Associates a key-value pair only if the key is not already present or its value is nil.n merge-nested, merge-nested-new: Recursively merge nested maps. merge-nested-new only merges if the target key doesn't exist.n dissoc-nested: Recursively disassociates keys from nested maps, removing empty intermediate maps.n flatten-nested: Flattens all elements of a collection into a single sequence.n tree-flatten, tree-nestify, tree-nestify:all: Convert between flat (path-based keys) and nested map representations.n reshape: Moves values within a map according to a specified transformation table.n find-templates, transform-fn, transform: Tools for defining and applying data transformations using template strings.n empty-record: Creates an empty instance of a defrecord.n transpose: Swaps keys and values in a map.n index-at, element-at: Find the index or element matching a predicate in a collection.n insert-at, remove-at: Insert or remove elements at a specific index in a vector.n deduped?: Checks if all elements in a collection are unique.n unfold: Generates a sequence by repeatedly applying a function to a seed value.n* diff, diff:changes, diff:new, diff:changed, diff:patch, diff:unpatch: Functions for computing differences between nested maps and applying/reverting those differences.

12.3    Usage Pattern:

This namespace is a utility belt for any Clojure project that deals heavily with data manipulation. It's particularly valuable for:

  • Configuration Management: Merging and transforming configuration maps.n State Management: Efficiently updating and tracking changes in complex application state.n Data Processing Pipelines: Building transformations for data flowing through an application.n API Development: Reshaping data structures for different API endpoints or external systems.n Metaprogramming: Manipulating data structures that represent code or schemas.

By providing these powerful and often recursive collection utilities, std.lib.collection significantly enhances Clojure's already strong data-oriented programming capabilities.