Skip to content

Instantly share code, notes, and snippets.

@bsless
Last active October 21, 2020 06:57
Show Gist options
  • Save bsless/e76a6537907ad4eecd69dcb321318b95 to your computer and use it in GitHub Desktop.
Save bsless/e76a6537907ad4eecd69dcb321318b95 to your computer and use it in GitHub Desktop.
Annotated version for Oded
(defn fold-by ;;; similar to group-by
([kf ;;; Key function to group on
op ;;; operation how to combine two elements under the same key
coll] ;; collection of maps
(persistent!
(reduce
(fn [ret x] ;; ret is a map from (kf x) to xs, similar to the result of group-by
(let [k (kf x) ;; derive the key
v (get ret k)] ;; get the value already associated with the key if it exists
(assoc! ret
k
(if (some? v) ;; if some value was found
(op v x) ;;; combine the found value with the new element from coll
(op x))))) ;; unary application, for completeness
(transient {}) ;;; go fast
coll))))
(defn- map-combiner
[m] ;;; Takes a map of keyword -> function
(completing ;;; takes a function of [a b], returns a function that can also take one arg, like + and conj do.
(fn [m1 m2] ;; return a function of two maps
(reduce-kv ;;; reduce over the map of functions
(fn [acc k f] ;;; build up accumulator. at key `k` with function `f`
(let [v1 (get m1 k) ;;; get the values associated with `k` from m1 and m2
v2 (get m2 k)]
(assoc acc k (f v1 v2)))) ;;; combine `v1` and `v2` with `f` and associate to `k`
{}
m))))
(defn foldmaps
[idx ;;; index function
ops ;;; map of keywords -> functions
ms] ;;; coll of maps
(vals ;;; get back only the values
(fold-by ;;; returns a map
idx ;;; the index function to group-by
(map-combiner ops) ;;; Derive a function from the kw -> f map
ms)))
(def ms [{:a 1 :b "b"} {:a 2 :b "c"} {:a 3 :b "b"} {:a 4 :b "c"} {:a 3 :b "d"}])
(defn- right [_ b] b)
(foldmaps :b {:a + :b right} ms)
;;; more sophisticated index:
(foldmaps (juxt :a :b) {:a + :b right} ms)
;;;; Expanded versions with rf's
(defn fold-by
([kf op coll]
(persistent!
(reduce
(fn [ret x]
(let [k (kf x)
v (get ret k)]
(assoc! ret k (if (some? v)
(op v x)
(op (op) x)))))
(transient {})
coll))))
(defn- map-combiner
[m]
(fn
([]
(reduce-kv
(fn [acc k f]
(assoc acc k (f)))
{}
m))
([rec]
(reduce-kv
(fn [acc k f]
(let [v (get rec k)]
(assoc acc k (f v))))
{}
m))
([m1 m2]
(reduce-kv
(fn [acc k f]
(let [v1 (get m1 k)
v2 (get m2 k)]
(assoc acc k (f v1 v2))))
{}
m))))
(defn foldmaps
[idx ops ms]
(vals (fold-by idx (map-combiner ops) ms)))
@ohassidi
Copy link

Wow man, thanks!!

@ohassidi
Copy link

What is line 42 for?

@bsless
Copy link
Author

bsless commented Jul 23, 2020

What is line 42 for?

just some dumb accumulator.

Newer version needs all functions to have 0 and 2 arity, where 0 creates an initial value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment