This repository has been archived by the owner on Jan 28, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
update_in.clj
121 lines (102 loc) · 3.2 KB
/
update_in.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
(ns detritus.update-in
"Stolen from zmaril (with permission)")
;; Got tired of falling back to a map when I needed to multiple things
;; update things the same way in nested data structures. update-in* is
;; like update-in but expanded to allow for iteration over all the
;; elements in a collection. Inspired by the pull api in datomic.
(defn keys* [m]
;; Only associative things can be assoc'd on and have keys
(when (associative? m)
;; FIXME: this is closed dispatch, but c.l.Associative doesn't
;; provide some sort of key sequence operation much to my
;; surprise and as noted clojure.core/keys isn't complete over
;; associative things.
;;
;; Vector is the only Associative defined in core which doesn't
;; comply with the implicit kv seq contract depended upon by
;; clojure.lang.RT/keys
(if (vector? m)
(range 0 (count m))
(keys m))))
(defn update-in*
([m [k & ks] f & args]
(if-not (associative? m)
;; Don't even try
m
;; Maps, vectors etc.
(cond
(and (not (nil? ks))
(= k :all))
,,(if (vector? m)
;; special case vectors because transient vectors aren't associative
(->> m
keys*
(map (fn [k] (apply update-in* (get m k) ks f args)))
(into []))
(as-> (transient (empty m)) t
(reduce (fn [m k] (assoc! m k (apply update-in* (get m k) ks f args)))
t (keys* m))
(persistent! t)))
(and (nil? ks)
(= k :all))
,,(if (vector? m)
;; special case vectors because transient vectors aren't associative
(->> m
keys*
(map (fn [k] (apply f (get m k) args)))
(into []))
;; general case of transient maps
(as-> (transient (empty m)) t
(reduce (fn [t k] (assoc t k (apply f (get m k) args)))
t (keys* m))
(persistent! t)))
(not (nil? ks))
,,(assoc m k (apply update-in* (get m k) ks f args))
(nil? ks)
,,(assoc m k (apply f (get m k) args))))))
(assert
(= (update-in* [1 2] [:all] inc)
[2 3]))
(assert
(= (update-in* [[1 2] [3 4]] [:all :all] inc)
[[2 3] [4 5]]))
(assert
(= (update-in* {:a [1 2]} [:a :all] inc)
{:a [2 3]}))
(assert
(= (update-in* {:a [{:id 1} {:id 2}]} [:a :all :id] inc)
{:a [{:id 2} {:id 3}]}))
(let [l (list 1 2 3)]
(assert
(= (update-in* l [:all] inc)
l)))
(let [n 15]
(assert
(= (update-in* n [:all] inc)
n)))
(defn breakout
([vs] (breakout -1 vs))
([n vs]
(->> (group-by first vs)
(map (fn [[k v]]
[k (let [rst (distinct (map rest v))]
(if (or (= 1 (count (first rst)))
(zero? n))
(apply concat rst)
(breakout (dec n) rst)))]))
(into {}))))
(defn prune [m]
(->> m
(map
(fn [[k vs]]
(if (seq? vs)
(if (= 1 (count vs))
[k (first vs)]
[k vs])
[k (prune vs)])))
(into {})))
(prune (breakout
[[1 1 1]
[1 2 1]
[2 1 1]
[2 3 1]]))