hara.data maps and representations of data

Author: Chris Zheng  (z@caudate.me)
Date: 29 June 2017
Repository: https://github.com/zcaudate/hara
Version: 2.5.10

hara.data consists of utility functions that act on clojure hash-maps and map-like representations of data. The level of complexity needed for working with data increases as it becomes nested and then relational.

1    data.map

Add to project.clj dependencies:

[im.chit/hara.data.map "2.5.10"]

hara.data.map contain functions for clojure maps



assoc-if ^

assoc key/value pairs to the map only on non-nil values

v 2.1
(defn assoc-if
  ([m k v]
     (if (not (nil? v)) (assoc m k v) m))
  ([m k v & more]
     (apply assoc-if (assoc-if m k v) more)))
link
(assoc-if {} :a 1) => {:a 1} (assoc-if {} :a 1 :b nil) => {:a 1}

assoc-in-if ^

assoc-in a nested key/value pair to a map only on non-nil values

v 2.1
(defn assoc-in-if
  [m arr v]
  (if (not (nil? v)) (assoc-in m arr v) m))
link
(assoc-in-if {} [:a :b] 1) => {:a {:b 1}} (assoc-in-if {} [:a :b] nil) => {}

assoc-in-nil ^

only assoc-in if the value in the original map is nil

v 2.1
(defn assoc-in-nil
  [m ks v]
  (if (not (nil? (get-in m ks))) m (assoc-in m ks v)))
link
(assoc-in-nil {} [:a :b] 2) => {:a {:b 2}} (assoc-in-nil {:a {:b 1}} [:a :b] 2) => {:a {:b 1}}

assoc-nil ^

only assoc if the value in the original map is nil

v 2.1
(defn assoc-nil
  ([m k v]
     (if (not (nil? (get m k))) m (assoc m k v)))
  ([m k v & more]
     (apply assoc-nil (assoc-nil m k v) more)))
link
(assoc-nil {:a 1} :b 2) => {:a 1 :b 2} (assoc-nil {:a 1} :a 2 :b 2) => {:a 1 :b 2}

dissoc-in ^

disassociates keys from a nested map. Setting `keep` to `true` will not remove a empty map after dissoc

v 2.1
(defn dissoc-in
  ([m [k & ks]]
     (if-not ks
       (dissoc m k)
       (let [nm (dissoc-in (m k) ks)]
         (cond (empty? nm) (dissoc m k)
               :else (assoc m k nm)))))

  ([m [k & ks] keep]
     (if-not ks
       (dissoc m k)
       (assoc m k (dissoc-in (m k) ks keep)))))
link
(dissoc-in {:a {:b 10 :c 20}} [:a :b]) => {:a {:c 20}} (dissoc-in {:a {:b 10}} [:a :b]) => {} (dissoc-in {:a {:b 10}} [:a :b] true) => {:a {}}

into-if ^

like into but filters nil values for both key/value pairs and sequences

v 2.1
(defn into-if
  [to from]
  (reduce (fn [i e]
            (if (or (and (coll? e) (not (nil? (second e))))
                    (and (not (coll? e)) (not (nil? e))))
              (conj i e)
              i))
          to from))
link
(into-if [] [1 nil 2 3]) => [1 2 3] (into-if {:a 1} {:b nil :c 2}) => {:a 1 :c 2}

merge-if ^

merges key/value pairs into a single map only if the value exists

v 2.1
(defn merge-if
  ([] nil)
  ([m]
     (reduce (fn [i [k v]]
               (if (not (nil? v)) (assoc i k v) i))
             {} m))
  ([m1 m2]
     (reduce (fn [i [k v]]
               (if (not (nil? v)) (assoc i k v) i))
             (merge-if m1) m2))
  ([m1 m2 & more]
     (apply merge-if (merge-if m1 m2) more)))
link
(merge-if {:a nil :b 1}) => {:b 1} (merge-if {:a 1} {:b nil :c 2}) => {:a 1 :c 2} (merge-if {:a 1} {:b nil} {:c 2}) => {:a 1 :c 2}

merge-nil ^

only merge if the value in the original map is nil

v 2.1
(defn merge-nil
  ([] nil)
  ([m] m)
  ([m1 m2]
     (reduce (fn [i [k v]]
               (if (not (nil? (get i k)))
                 i
                 (assoc i k v)))
             m1 m2))
  ([m1 m2 & more]
     (apply merge-nil (merge-nil m1 m2) more)))
link
(merge-nil {:a 1} {:b 2}) => {:a 1 :b 2} (merge-nil {:a 1} {:a 2}) => {:a 1}

retract-in ^

reversed the changes by transform-in

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

select-keys-if ^

selects only the non-nil key/value pairs from a map

v 2.1
(defn select-keys-if
  [m ks]
  (reduce (fn [i k]
            (let [v (get m k)]
              (if (not (nil? v))
                (assoc i k v)
                i)))
          nil ks))
link
(select-keys-if {:a 1 :b nil} [:a :b]) => {:a 1} (select-keys-if {:a 1 :b nil :c 2} [:a :b :c]) => {:a 1 :c 2}

transform-in ^

moves values around in a map according to a table

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

unique ^

returns a map of all key/value pairs that differ from a second map

v 2.1
(defn unique
  [m1 m2]
  (reduce (fn [i [k v]]
            (if (not= v (get m2 k))
              (assoc i k v)
              i))
          nil m1))
link
(unique {:a 1} {:a 2}) => {:a 1} (unique {:a 1 :b 2} {:b 2}) => {:a 1} (unique {:b 2} {:b 2 :a 1}) => nil

update-in-if ^

update-in a nested key/value pair only if the value exists

v 2.1
(defn update-in-if
  [m arr f & args]
  (let [v (get-in m arr)]
    (if (not (nil? v))
      (assoc-in m arr (apply f v args))
      m)))
link
(update-in-if {:a {:b 1}} [:a :b] inc) => {:a {:b 2}} (update-in-if {} [:a :b] inc) => {}

2    data.seq

Add to project.clj dependencies:

[im.chit/hara.data.seq "2.5.10"]

hara.data.seq contain functions for sequences and arrays



positions ^

find positions of elements matching the predicate

v 2.2
(defn positions
  [pred coll]
  (keep-indexed (fn [idx x]
                  (when (pred x)
                    idx))
                coll))
link
(positions even? [5 5 4 4 3 3 2 2]) => [2 3 6 7]

remove-index ^

removes element at the specified index

v 2.2
(defn remove-index
  [coll i]
  (cond (vector? coll)
        (reduce conj
                (subvec coll 0 i)
                (subvec coll (inc i) (count coll)))

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

3    data.nested

Add to project.clj dependencies:

[im.chit/hara.data.nested "2.5.10"]

hara.data.nested contain functions for updating nested hashmaps.



clean-nested ^

returns a associative with nils and empty hash-maps removed.

v 2.1
(defn clean-nested
  ([m] (clean-nested m (constantly false)))
  ([m prchk]
   (reduce-kv (fn [out k v]
                (cond (or (nil? v)
                          (suppress (expr/check-> m prchk)))
                      out

                      (hash-map? v)
                      (let [subv (clean-nested v prchk)]
                        (if (empty? subv)
                          out
                          (assoc out k subv)))

                      :else
                      (assoc out k v)))
              {}
              m)))
link
(clean-nested {:a {:b {:c {}}}}) => {} (clean-nested {:a {:b {:c {} :d 1 :e nil}}}) => {:a {:b {:d 1}}}

dissoc-nested ^

returns `m` without all nested keys in `ks`.

v 2.1
(defn dissoc-nested
  [m ks]
  (let [ks (if (set? ks) ks (set ks))]
    (reduce-kv (fn [out k v]
                 (cond (get ks k)
                       out

                       (hash-map? v)
                       (assoc out k (dissoc-nested v ks))

                       :else (assoc out k v)))
               {}
               m)))
link
(dissoc-nested {:a {:b 1 :c {:b 1}}} [:b]) => {:a {:c {}}}

key-paths ^

the set of all paths in a map, governed by a max level of nesting

v 2.1
(defn key-paths
  ([m] (key-paths m -1 []))
  ([m max] (key-paths m max []))
  ([m max arr]
   (reduce-kv (fn [out k v]
                (cond (and (not= max 1)
                           (hash-map? v))
                      (vec (concat out (key-paths v (dec max) (conj arr k))))

                      :else (conj out (conj arr k))))
              []
              m)))
link
(key-paths {:a {:b 1} :c {:d 1}}) => (contains [[:c :d] [:a :b]] :in-any-order) (key-paths {:a {:b 1} :c {:d 1}} 1) => (contains [[:c] [:a]] :in-any-order)

keys-nested ^

the set of all nested keys in a map

v 2.1
(defn keys-nested
  ([m] (reduce-kv (fn [s k v]
                    (if (hash-map? v)
                      (set/union (conj s k) (keys-nested v))
                      (conj s k)))
                  #{}
                  m)))
link
(keys-nested {:a {:b 1 :c {:d 1}}}) => #{:a :b :c :d}

merge-nested ^

merges nested values from left to right.

v 2.1
(defn merge-nested
  ([] {})
  ([m] m)
  ([m1 m2]
   (reduce-kv (fn [out k v]
                (let [v1 (get out k)]
                  (cond (nil? v1)
                        (assoc out k v)

                        (and (hash-map? v) (hash-map? v1))
                        (assoc out k (merge-nested v1 v))

                        (= v v1)
                        out

                        :else
                        (assoc out k v))))
              (or m1 {})
              m2))
  ([m1 m2 & ms]
     (apply merge-nested (merge-nested m1 m2) ms)))
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-nil-nested ^

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

v 2.1
(defn merge-nil-nested
  ([] nil)
  ([m] m)
  ([m1 m2]
   (reduce-kv (fn [out k v]
                (let [v1 (get out k)]
                  (cond (nil? v1)
                        (assoc out k v)

                        (and (hash-map? v) (hash-map? v1))
                        (assoc out k (merge-nil-nested v1 v))

                        :else
                        out)))
              (or m1 {})
              m2))
  ([m1 m2 & more]
     (apply merge-nil-nested (merge-nil-nested m1 m2) more)))
link
(merge-nil-nested {:a {:b 2}} {:a {:c 2}}) => {:a {:b 2 :c 2}} (merge-nil-nested {:b {:c :old}} {:b {:c :new}}) => {:b {:c :old}}

unique-nested ^

all nested values in `m1` that are unique to those in `m2`.

v 2.1
(defn unique-nested
  [m1 m2]
  (reduce-kv (fn [out k v]
               (let [v2 (get m2 k)]
                 (cond (nil? v2)
                       (assoc out k v)

                       (and (hash-map? v) (hash-map? v2))
                       (let [subv (unique-nested v v2)]
                         (if (empty? subv)
                           out
                           (assoc out k subv)))


                       (= v v2)
                       out

                       :else
                       (assoc out k v))))
             {}
             m1))
link
(unique-nested {:a {:b 1}} {:a {:b 1 :c 1}}) => {} (unique-nested {:a {:b 1 :c 1}} {:a {:b 1}}) => {:a {:c 1}}

update-keys-in ^

updates all keys in a map with given function

v 2.1
(defn update-keys-in
  [m arr f & args]
  (let [key-fn (fn [m]
                 (reduce-kv (fn [m k v]
                              (assoc m (apply f k args) v))
                            {}
                            m))]
    (cond (sequential? arr)
          (if (empty? arr)
            (key-fn m)
            (update-in m arr key-fn))

          (number? arr)
          (if (= 1 arr)
            (key-fn m)
            (reduce (fn [out kpath]
                      (update-in out kpath key-fn))
                    m
                    (key-paths m (dec arr))))

          :else (throw (Exception. (str "`arr` has to be a seq or a number not " arr))))))
link
(update-keys-in {:x {["a" "b"] 1 ["c" "d"] 2}} [:x] string/join) => {:x {"ab" 1 "cd" 2}} (update-keys-in {:a {:c 1} :b {:d 2}} 2 name) => {:b {"d" 2}, :a {"c" 1}}

update-vals-in ^

updates all values in a map with given function

v 2.1
(defn update-vals-in
  [m arr f & args]
  (let [val-fn (fn [m]
                 (reduce-kv (fn [m k v]
                              (assoc m k (apply f v args)))
                            {}
                            m))]
    (cond (sequential? arr)
          (if (empty? arr)
            (val-fn m)
            (update-in m arr val-fn))

          (number? arr)
          (if (= 1 arr)
            (val-fn m)
            (reduce (fn [out kpath]
                      (update-in out kpath val-fn))
                    m
                    (key-paths m (dec arr))))

          :else (throw (Exception. (str "`arr` has to be a seq or a number not " arr))))))
link
(update-vals-in {:a 1 :b 2} [] inc) => {:a 2 :b 3} (update-vals-in {:a {:c 1} :b 2} [:a] inc) => {:a {:c 2} :b 2} (update-vals-in {:a {:c 1} :b {:d 2}} 2 inc) => {:a {:c 2} :b {:d 3}} (update-vals-in {:a 1 :b 2} 1 inc) => {:a 2, :b 3}

4    data.diff

Add to project.clj dependencies:

[im.chit/hara.data.diff "2.5.10"]

hara.data.diff contain functions for comparing maps, as well as functions to patch changes.



changed ^

outputs what has changed between the two maps

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

diff ^

finds the difference between two maps

v 2.1
(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-changes ^

finds changes in nested maps, does not consider new elements

v 2.1
(defn diff-changes
  ([m1 m2]
   (diff-changes m1 m2 []))
  ([m1 m2 arr]
   (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)))

                          (= 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-new ^

finds new elements in nested maps, does not consider changes

v 2.1
(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}

patch ^

use the diff to convert one map to another in the forward direction based upon changes between the two.

v 2.1
(defn 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]
                   (map/dissoc-in m arr))
                    %
                    (keys (:- diff))))))
link
(let [m1 {:a {:b 1 :d 3}} m2 {:a {:c 2 :d 4}} df (diff m2 m1)] (patch m1 df)) => {:a {:c 2 :d 4}}

unpatch ^

use the diff to convert one map to another in the reverse direction based upon changes between the two.

v 2.1
(defn 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]
                   (map/dissoc-in m arr))
                    %
                    (keys (:+ diff))))))
link
(let [m1 {:a {:b 1 :d 3}} m2 {:a {:c 2 :d 4}} df (diff m2 m1 true)] (unpatch m2 df)) => {:a {:b 1 :d 3}}

5    data.combine

Add to project.clj dependencies:

[im.chit/hara.data.combine "2.5.10"]

hara.data.combine contains functions for working with sets of data.



combine ^

takes `v1` and `v2`, which can be either values or sets of values and merges them into a new set.

v 2.1
(defn combine
  ([] nil)
  ([m] m)
  ([v1 v2]
     (cond (nil? v2) v1
           (nil? v1) v2

           (set? v1)
           (cond (set? v2)
                 (set/union v1 v2)
                 :else (conj v1 v2))

           :else
           (cond (set? v2)
                 (conj v2 v1)

                 (= v1 v2) v1
                 :else #{v1 v2})))

  ([v1 v2 sel func]
     (-> (cond (nil? v2) v1
               (nil? v1) v2
               (set? v1)
               (cond (set? v2)
                     (combine-set v1 v2 sel func)

                     :else (combine-value v1 v2 sel func))
               :else
               (cond (set? v2)
                     (combine-value v2 v1 sel func)

                     (eq-> v1 v2 sel)
                     (func v1 v2)

                     (= v1 v2) v1
                     :else #{v1 v2}))
         (combine-internal sel func))))
link
(combine 1 2) => #{1 2} (combine #{1} 1) => #{1} (combine #{{:id 1} {:id 2}} #{{:id 1 :val 1} {:id 2 :val 2}} :id merge) => #{{:id 1 :val 1} {:id 2 :val 2}}

combine-internal ^

combines all elements in a single using sel and func

v 2.1
(defn combine-internal
  [set sel rd]
  (if-not (set? set) set
          (combine-set #{} set sel rd)))
link
(combine-internal #{{:id 1} {:id 2} {:id 1 :val 1} {:id 2 :val 2}} :id merge) => #{{:id 1 :val 1} {:id 2 :val 2}}

combine-select ^

selects an element out of the set that matches sel when it is applied

v 2.1
(defn combine-select
  [set val sel]
  (->> set
       (filter (fn [v]
                 (eq-> v val sel)))
       first))
link
(combine-select #{1 2 3} 2 identity) => 2 (combine-select #{{:id 1 :val 2} {:id 2 :val 2}} {:id 1 :val 1} :id) => {:id 1 :val 2}

combine-set ^

returns the combined set of `s1` and `s2` using sel for item comparison and func as the combine function

v 2.1
(defn combine-set
  [s1 s2 sel func]
  (reduce (fn [out v]
            (combine-value out v sel func))
   s1 s2))
link
(combine-set #{{:id 1 :val 0} {:id 2 :a 0}} #{{:id 1 :val 1} {:id 2 :val 2}} :id merge) => #{{:id 1 :val 1} {:id 2 :val 2 :a 0}}

combine-value ^

returns a single set, sel is used for item comparison while func is used as the combine function

v 2.1
(defn combine-value
  [set val sel func]
  (if-let [sv (combine-select set val sel)]
    (conj (disj set sv) (func sv val))
    (conj set val)))
link
(combine-value #{{:id 1 :a 1} {:id 2 :a 2}} {:id 3 :b 3} :id merge) => #{{:id 1, :a 1} {:id 2, :a 2} {:id 3, :b 3}} (combine-value #{{:id 1 :a 1} {:id 2 :a 2}} {:id 1 :b 3} :id merge) => #{{:id 1 :a 1 :b 3} {:id 2 :a 2}}

decombine ^

takes set or value `v` and returns a set with elements matching sel removed

v 2.1
(defn decombine
  [v dv]
  (cond (set? v)
        (let [res (cond (set? dv)
                        (set/difference v dv)

                        (ifn? dv)
                        (set (filter (complement dv) v))

                        :else (disj v dv))]
          (if-not (empty? res) res))
        :else
        (if-not (check-> v dv) v)))
link
(decombine 1 1) => nil (decombine 1 2) => 1 (decombine #{1} 1) => nil (decombine #{1 2 3 4} #{1 2}) => #{3 4} (decombine #{1 2 3 4} even?) => #{1 3}

6    data.complex

Add to project.clj dependencies:

[im.chit/hara.data.complex "2.5.10"]

hara.data.complex contain functions for working with relational data such as that coming out from datomic.



assocs ^

similar to `assoc` but conditions of association is specified through `sel` (default: `identity`) and well as merging specified through `func` (default: `combine`).

v 2.1
(defn assocs
  ([m k v] (assocs m k v identity combine))
  ([m k v sel func]
   (let [z (get m k)]
     (cond (nil? z) (assoc m k v)
           :else
           (assoc m k (combine z v sel func))))))
link
(assocs {:a #{1}} :a #{2 3 4}) => {:a #{1 2 3 4}} (assocs {:a {:id 1}} :a {:id 1 :val 1} :id merge) => {:a {:val 1, :id 1}} (assocs {:a #{{:id 1 :val 2} {:id 1 :val 3}}} :a {:id 1 :val 4} :id merges) => {:a #{{:id 1 :val #{2 3 4}}}}

assocs-in ^

similar to assoc-in but can move through sets

v 2.1
(defn assocs-in
  ([m all-ks v] (assocs-in m all-ks v identity combine))
  ([m [k & ks :as all-ks] v sel func]
     (cond (nil? ks)
           (cond (vector? k) (error "cannot allow vector-form on last key " k)
                 (or (nil? m) (hash-map? m)) (assocs m k v sel func)
                 (nil? k) (combine m v sel func)
                 :else (error m " is not an associative map"))

           (or (nil? m) (hash-map? m))
           (cond (vector? k) (assocs-in-filtered m all-ks v sel func)
                 :else
                 (let [val (get m k)]
                   (cond (set? val)
                         (assoc m k (set (map #(assocs-in % ks v sel func) val)))
                         :else (assoc m k (assocs-in val ks v sel func)))))
           :else (error m " is required to be a map"))))
link
(assocs-in {:a {:b 1}} [:a :b] 2) => {:a {:b #{1 2}}} (assocs-in {:a #{{:b 1}}} [:a :b] 2) => {:a #{{:b #{1 2}}}} (assocs-in {:a #{{:b {:id 1}} {:b {:id 2}}}} [:a [:b [:id 1]] :c] 2) => {:a #{{:b {:id 1 :c 2}} {:b {:id 2}}}}

dissocs ^

similar to `dissoc` but allows dissassociation of sets of values from a map.

v 2.1
(defn dissocs
  [m k]
  (cond (vector? k)
        (let [[k v] k
              z (get m k)
              res (decombine z v)]
          (if (nil? res)
            (dissoc m k)
            (assoc m k res)))
        :else
        (dissoc m k)))
link
(dissocs {:a 1} :a) => {} (dissocs {:a #{1 2}} [:a #{0 1}]) => {:a #{2}} (dissocs {:a #{1 2}} [:a #{1 2}]) => {}

dissocs-in ^

similiar to `dissoc-in` but can move through sets.

v 2.1
(defn dissocs-in
  [m [k & ks :as all-ks]]
  (cond (nil? ks) (dissocs m k)

        (vector? k) (dissocs-in-filtered m all-ks)

        :else
        (let [val (get m k)]
          (cond (set? val)
                (assoc m k (set (map #(dissocs-in % ks) val)))
                :else (assoc m k (dissocs-in m ks))))))
link
(dissocs-in {:a #{{:b 1 :c 1} {:b 2 :c 2}}} [:a :b]) => {:a #{{:c 1} {:c 2}}} (dissocs-in {:a #{{:b #{1 2 3} :c 1} {:b #{1 2 3} :c 2}}} [[:a [:c 1]] [:b 1]]) => {:a #{{:b #{2 3} :c 1} {:b #{1 2 3} :c 2}}}

gets ^

returns the associated values either specified by a key or a key and predicate pair.

v 2.1
(defn gets
  [m k]
  (if-not (vector? k)
    (get m k)
    (let [[k prchk] k
          val (get m k)]
      (if-not (set? val) val
              (-> (filter #(check?-> % prchk) val) set)))))
link
(gets {:a 1} :a) => 1 (gets {:a #{0 1}} [:a zero?]) => #{0} (gets {:a #{{:b 1} {}}} [:a :b]) => #{{:b 1}}

gets-in ^

similar in style to `get-in` with operations on sets. returns a set of values.

v 2.1
(defn gets-in
  [m ks]
  (-> (gets-in-loop m ks) set (disj nil)))
link
(gets-in {:a 1} [:a]) => #{1} (gets-in {:a 1} [:b]) => #{} (gets-in {:a #{{:b 1} {:b 2}}} [:a :b]) => #{1 2}

merges ^

like `merge` but works across sets and will also combine duplicate key/value pairs together into sets of values.

v 2.1
(defn merges
  ([m1 m2] (merges m1 m2 identity combine))
  ([m1 m2 sel] (merges m1 m2 sel combine))
  ([m1 m2 sel func]
   (reduce-kv (fn [out k v]
                (assoc out k (combine (get out k) v sel func)))
              m1
              m2)))
link
(merges {:a 1} {:a 2}) => {:a #{1 2}} (merges {:a #{{:id 1 :val 1}}} {:a {:id 1 :val 2}} :id merges) => {:a #{{:id 1 :val #{1 2}}}}

merges-nested ^

like `merges` but works on nested maps

v 2.1
(defn merges-nested
  ([] nil)
  ([m] m)
  ([m1 m2] (merges-nested m1 m2 identity combine))
  ([m1 m2 sel] (merges-nested m1 m2 sel combine))
  ([m1 m2 sel func]
   (reduce-kv (fn [out k v]
                (let [v1 (get out k)]
                  (cond (not (and (hash-map? v1) (hash-map? v)))
                        (assoc out k (combine v1 v sel func))

                        :else
                        (assoc out k (merges-nested v1 v sel func)))))
              m1
              m2)))
link
(merges-nested {:a {:b 1}} {:a {:b 2}}) => {:a {:b #{1 2}}} (merges-nested {:a #{{:foo #{{:bar #{{:baz 1}}}}}}} {:a #{{:foo #{{:bar #{{:baz 2}}}}}}} hash-map? merges-nested) => {:a #{{:foo #{{:bar #{{:baz 2}}} {:bar #{{:baz 1}}}}}}}

merges-nested* ^

like `merges-nested but can recursively merge nested sets and values

v 2.1
(defn merges-nested*
  ([] nil)
  ([m] m)
  ([m1 m2] (merges-nested* m1 m2 hash-map? combine))
  ([m1 m2 sel] (merges-nested* m1 m2 sel combine))
  ([m1 m2 sel func]
   (reduce-kv (fn [out k v]
                (let [v1 (get out k)]
                  (cond (and (hash-map? v1) (hash-map? v))
                        (assoc out k (merges-nested* v1 v sel func))

                        (or (set? v1) (set? v))
                        (assoc out k (combine v1 v sel #(merges-nested* %1 %2 sel func)))

                        :else
                        (assoc out k (func v1 v)))))
              m1
              m2)))
link
(merges-nested* {:a #{{:id 1 :foo #{{:id 2 :bar #{{:id 3 :baz 1}}}}}}} {:a {:id 1 :foo {:id 2 :bar {:id 3 :baz 2}}}} :id) => {:a #{{:id 1 :foo #{{:id 2 :bar #{{:id 3 :baz #{1 2}}}}}}}}

7    data.record

Add to project.clj dependencies:

[im.chit/hara.data.record "2.5.10"]

hara.data.record contain functions for working with clojure records



empty ^

creates an empty record from an existing one

v 2.1
(defn empty
  [v]
  (.invoke ^java.lang.reflect.Method
           (.getMethod ^Class (type v) "create"
                       (into-array Class [clojure.lang.IPersistentMap]))
           nil
           (object-array [{}])))
link
(defrecord Database [host port]) (record/empty (Database. "localhost" 8080)) => (just {:host nil :port nil})

8    data.path

Add to project.clj dependencies:

[im.chit/hara.data.path "2.5.10"]

hara.data.pathconcerns itself with the translation between data contained in a nested versus data contained in a single map with paths as keys.

hara.data.path



contains-ns-key? ^

returns `true` if any key in map contains a namespace value

v 2.1
(defn contains-ns-key?
  [fm ns]
  (some #(path/path-ns? % ns) (keys fm)))
link
(contains-ns-key? {:hello/a 1 :hello/b 2 :there/a 3 :there/b 4} :hello) => true

flatten-keys ^

takes map `m` and flattens the first nested layer onto the root layer.

v 2.1
(defn flatten-keys
  ([m]
   (reduce-kv (fn [m k v]
                (if (hash-map? v)
                  (reduce-kv (fn [m sk sv]
                               (assoc m (path/join [k sk]) sv))
                             m
                             v)
                  (assoc m k v)))
              {}
              m)))
link
(flatten-keys {:a {:b 2 :c 3} :e 4}) => {:a/b 2 :a/c 3 :e 4} (flatten-keys {:a {:b {:c 3 :d 4} :e {:f 5 :g 6}} :h {:i 7} :j 8}) => {:a/b {:c 3 :d 4} :a/e {:f 5 :g 6} :h/i 7 :j 8}

flatten-keys-nested ^

returns a single associative map with all of the nested keys of `m` flattened. If `keep` is added, it preserves all the empty sets

v 2.1
(defn flatten-keys-nested
  ([m] (flatten-keys-nested m -1 false))
  ([m max keep-empty]
   (-> (pathify-keys-nested m max keep-empty)
       (nested/update-keys-in [] path/join))))
link
(flatten-keys-nested {"a" {"b" {"c" 3 "d" 4} "e" {"f" 5 "g" 6}} "h" {"i" {}}}) => {"a/b/c" 3 "a/b/d" 4 "a/e/f" 5 "a/e/g" 6} (flatten-keys-nested {"a" {"b" {"c" 3 "d" 4} "e" {"f" 5 "g" 6}} "h" {"i" {}}} -1 true) => {"a/b/c" 3 "a/b/d" 4 "a/e/f" 5 "a/e/g" 6 "h/i" {}}

group-by-set ^

returns a map of the elements of coll keyed by the result of f on each element. The value at each key will be a set of the corresponding elements, in the order they appeared in coll.

v 2.1
(defn group-by-set
  [f coll]
  (persistent!
   (reduce
    (fn [ret x]
      (let [k (f x)]
        (assoc! ret k (conj (get ret k #{}) x))))
    (transient {}) coll)))
link
(group-by-set even? [1 2 3 4 5]) => {false #{1 3 5}, true #{2 4}}

group-keys ^

returns the set of keys in `fm` that has keyword namespace of `ns`

v 2.1
(defn group-keys
  ([fm] (let [ks (keys fm)]
          (group-by-set #(path/path-ns %) ks)))
  ([fm ns]
     (let [ks (keys fm)]
       (->> ks
            (filter #(= ns (path/path-ns %)))
            set))))
link
(group-keys {:hello/a 1 :hello/b 2 :there/a 3 :there/b 4}) => {:there #{:there/a :there/b}, :hello #{:hello/b :hello/a}} (group-keys {:hello/a 1 :hello/b 2 :there/a 3 :there/b 4} :hello) => #{:hello/a :hello/b}

list-ns-keys ^

returns the set of keyword namespaces within a map

v 2.1
(defn list-ns-keys
  [fm]
  (let [ks (keys fm)]
    (set (map path/path-ns ks))))
link
(list-ns-keys {:hello/a 1 :hello/b 2 :there/a 3 :there/b 4}) => #{:hello :there}

nest-keys ^

returns a map that takes `m` and extends all keys with the `nskv` vector. `ex` is the list of keys that are not extended.

v 2.1
(defn nest-keys
  ([m nskv] (nest-keys m nskv []))
  ([m nskv ex]
    (let [e-map (select-keys m ex)
          x-map (apply dissoc m ex)]
      (merge e-map (if (empty? nskv)
                     x-map
                     (assoc-in {} nskv x-map))))))
link
(nest-keys {:a 1 :b 2} [:hello :there]) => {:hello {:there {:a 1 :b 2}}} (nest-keys {:there 1 :b 2} [:hello] [:there]) => {:hello {:b 2} :there 1}

pathify-keys-nested ^

converts a nested map structure into a flat map structure

v 2.1
(defn pathify-keys-nested
  ([m] (pathify-keys-nested m -1 false []))
  ([m max] (pathify-keys-nested m max false []))
  ([m max keep-empty] (pathify-keys-nested m max keep-empty []))
  ([m max keep-empty arr]
   (reduce-kv (fn [m k v]
                (if (or (and (not (> 0 max))
                             (<= max 1))
                        (not (hash-map? v))
                        (and keep-empty
                             (empty? v)))
                  (assoc m (conj arr k) v)
                  (merge m (pathify-keys-nested v (dec max) keep-empty (conj arr k)))))
              {}
              m)))
link
(pathify-keys-nested {:a {:b {:c 1}}}) => {[:a :b :c] 1} (pathify-keys-nested {:a {:b {:c 1}}} 2) => {[:a :b] {:c 1}} (pathify-keys-nested {:a {:b {:c 1 :d 3 :e {}}}} -1 true) => {[:a :b :c] 1, [:a :b :d] 3, [:a :b :e] {}}

treeify-keys ^

returns a nested map, expanding out the first level of keys into additional hash-maps.

v 2.1
(defn treeify-keys
  [m]
  (reduce-kv (fn [m k v]
               (assoc-in m (path/split k) v))
             {}
             m))
link
(treeify-keys {:a/b 2 :a/c 3}) => {:a {:b 2 :c 3}} (treeify-keys {:a/b {:e/f 1} :a/c {:g/h 1}}) => {:a {:b {:e/f 1} :c {:g/h 1}}}

treeify-keys-nested ^

returns a nested map, expanding out all levels of keys into additional hash-maps.

v 2.1
(defn treeify-keys-nested
  [m]
  (reduce-kv (fn [m k v]
               (if (and (hash-map? v) (not (empty? v)))
                 (update-in m (path/split k) nested/merge-nested (treeify-keys-nested v))
                 (assoc-in m (path/split k) v)))
             {}
             m))
link
(treeify-keys-nested {:a/b 2 :a/c 3}) => {:a {:b 2 :c 3}} (treeify-keys-nested {:a/b {:e/f 1} :a/c {:g/h 1}}) => {:a {:b {:e {:f 1}} :c {:g {:h 1}}}}

unnest-keys ^

the reverse of `nest-keys`. Takes `m` and returns a map with all keys with a `keyword-nsvec` of `nskv` being 'unnested'

v 2.1
(defn unnest-keys
  ([m nskv] (unnest-keys m nskv []))
  ([m nskv ex]
   (let [tm     (treeify-keys-nested m)
         c-map  (get-in tm nskv)
         x-map  (map/dissoc-in tm nskv)]
    (merge c-map (if (empty? ex)
                   x-map
                   (assoc-in {} ex x-map))))))
link
(unnest-keys {:hello/a 1 :hello/b 2 :there/a 3 :there/b 4} [:hello]) => {:a 1 :b 2 :there {:a 3 :b 4}} (unnest-keys {:hello {:there {:a 1 :b 2}} :again {:c 3 :d 4}} [:hello :there] [:+] ) => {:a 1 :b 2 :+ {:again {:c 3 :d 4}}}

9    data.transform

Add to project.clj dependencies:

[im.chit/hara.data.map "2.5.10"]

hara.data.transform contain functions for transforming maps



find-templates ^

finds the template with associated path

v 2.5
(defn find-templates
  ([m]
   (find-templates m [] {}))
  ([m path saved]
   (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]}

template? ^

checks if an object is a template

v 2.5
(defn template?
  [s]
  (and (string? s)
       (.startsWith s "{{")
       (.endsWith s "}}")))
link
(template? "{{template}}") => true (template? :not-one) => false

transform ^

creates a transformation function

v 2.5
(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 {:type :email :user {:hash "{{hash}}" :salt "{{salt}}"} :value "{{email}}"}}} [:keystore :db] {:hash "1234" :salt "ABCD" :email "a@a.com"}) => {:login {:type :email, :user {:hash "1234", :salt "ABCD"}, :value "a@a.com"}}

transform-fn ^

creates a transformation function

v 2.5
(defn transform-fn
  [schema [from to]]
  (let [from-template (find-templates (get schema from))
        to-template   (find-templates (get schema to))]
    (fn [data]
      (reduce (fn [out k]
                   (assoc-in out
                             (get to-template k)
                             (->> (get from-template k)
                                  (get-in data))))
                 (get schema to)
                 (keys to-template)))))
link
((transform-fn {:keystore {:hash "{{hash}}" :salt "{{salt}}" :email "{{email}}"} :db {:login {:type :email :user {:hash "{{hash}}" :salt "{{salt}}"} :value "{{email}}"}}} [:keystore :db]) {:hash "1234" :salt "ABCD" :email "a@a.com"}) => {:login {:type :email, :user {:hash "1234", :salt "ABCD"}, :value "a@a.com"}}