-
Notifications
You must be signed in to change notification settings - Fork 5
/
map.clj
306 lines (278 loc) · 12 KB
/
map.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
(ns io.randomseed.bankster.util.map
(:require [io.randomseed.bankster.util :refer :all]))
(defmacro lazy-get
"Like get but the default value is not evaluated if the key is found."
{:added "1.0.0"}
[m k exp]
`(let [m# ~m, k# ~k]
(if (and (associative? m#) (contains? m# k#)) (get m# k#) ~exp)))
(defn update-existing
"Updates the key k of the given collection coll by calling a function fun and passing
optional arguments specified as additional arguments. Will not perform any update
if the given key does not exist within the collection. Returns a collection."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap coll k fun & more]
(if (contains? coll k)
(let [fun (if (fn? fun) fun (constantly fun))]
(apply update coll k fun more))
coll))
(defn update-missing
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[coll k fun & more]
(if-not (contains? coll k)
(let [fun (if (fn? fun) (fn [& args] (apply fun (next args))) (constantly fun))]
(apply update coll k fun more))
coll))
(defmacro assoc-if
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([coll pred k val]
`(let [kol# ~coll] (if ~pred (assoc kol# ~k ~val) kol#))))
(defmacro assoc-if-not
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([coll pred k val]
`(let [kol# ~coll] (if ~pred kol# (assoc kol# ~k ~val)))))
(defmacro assoc-if-key
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([coll k pred val]
`(let [kol# ~coll key# ~k]
(if (~pred (get kol# key#)) (assoc kol# key# ~val) kol#))))
(defmacro assoc-if-not-key
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[coll k pred val]
`(let [kol# ~coll key# ~k]
(if (~pred (get kol# key#)) kol# (assoc kol# key# ~val))))
(defn ^clojure.lang.IPersistentMap remove-if-value
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m
^clojure.lang.IFn pred]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v]
(if (pred v) (dissoc mp k) mp))
m m))
(defn ^clojure.lang.IPersistentMap remove-if-value-in
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m vals]
(let [vset (set vals)]
(remove-if-value m #(contains? vset %))))
(defn ^clojure.lang.IPersistentMap remove-if-value-not-in
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m vals]
(let [vset (set vals)
not-contains? (complement contains?)]
(remove-if-value m #(not-contains? vset %))))
(defn ^clojure.lang.IPersistentMap remove-except
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m ^clojure.lang.ISeq keyseq]
(select-keys m keyseq))
(defn ^clojure.lang.IPersistentMap remove-by-if-value-in
"Removes map entries if the given predicate returns true and value is in the given
set."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m
^clojure.lang.IFn pred
^clojure.lang.PersistentHashSet only]
(reduce #(if (pred (get %1 %2)) (dissoc %1 %2) %1) m only))
(defn ^clojure.lang.IPersistentMap remove-empty-values
"Removes entries with empty values from a map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
(^clojure.lang.IPersistentMap [^clojure.lang.IPersistentMap m]
(remove-if-value m #(or (nil? %)
(and (counted? %) (< (count %) 1))
(and (seqable? %) (nil? (seq %))))))
(^clojure.lang.IPersistentMap [^clojure.lang.IPersistentMap m
^clojure.lang.PersistentHashSet only]
(remove-by-if-value-in m #(or (nil? %)
(and (counted? %) (< (count %) 1))
(and (seqable? %) (nil? (seq %)))) only)))
(defn map-vals-by-k
"For each key and value of the given map m calls a function passed as the first
argument (passing successive keys during calls to it) and generates a map with
values updated by results returned by the function. When the third argument is
given it should be a map on which operations are performed instead of using the
original map. This may be helpful when we want to avoid merging the results with
another map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-vals-by-k f m m))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v] (assoc mp k (f k)))
dst m)))
(defn map-vals-by-kv
"For each key and value of the given map m calls a function passed as the first
argument (passing successive keys and values during calls to it) and generates a
map with values updated by results returned by the function. When the third
argument is given it should be a map on which operations are performed instead of
using the original map. This may be helpful when we want to avoid merging the
results with another map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-vals-by-kv f m m))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v] (assoc mp k (f k v)))
dst m)))
(defn map-vals
"For each key and value of the given map m calls a function passed as the first
argument (passing successive values during calls to it) and generates a map with
values updated by results returned by the function. When the third argument is
given it should be a map on which operations are performed instead of using the
original map. This may be helpful when we want to avoid merging the results with
another map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-vals f m m))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v] (assoc mp k (f v)))
dst m)))
(defn map-keys-by-v
"For each key and value of the given map m calls a function passed as the first
argument (passing successive values during calls to it) and generates a map with
keys updated by results returned by the function. When the third argument is
given then it should be a map on which operations are performed instead of using
and empty map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-keys-by-v f m {}))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v] (assoc mp (f v) v))
dst m)))
(defn map-keys
"For each key and value of the given map m calls a function passed as the first
argument (passing successive keys during calls to it) and generates a map with keys
updated by results returned by the function. When the third argument is given then
it should be a map on which operations are performed instead of using an empty
map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-keys f m {}))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v] (assoc mp (f k) v))
dst m)))
(defn map-keys-and-vals
"For each key and value of the given map m calls a function passed as the first
argument (passing successive keys during calls to it) and generates a map with keys
updated by results returned by the function and values also updated by results of
the same function. The function should return a sequential collection of 2
elements: first containing a new value of a key and second containing a new value
of a transformed value associated with that key. When the third argument is given
then it should be a map on which operations are performed instead of using an empty
map."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m]
(map-keys-and-vals f m {}))
([^clojure.lang.IFn f
^clojure.lang.IPersistentMap m
^clojure.lang.IPersistentMap dst]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v]
(let [[new-k new-v] (f k v)] (assoc mp new-k new-v)))
dst m)))
(defn map-of-sets-invert
"Like `clojure.set/map-invert` but for map of sets (as values) to preserve all
possible values (as keys of newly created map)."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m]
(reduce (fn [^clojure.lang.IPersistentMap am [k v]]
(assoc am k (conj (am k (hash-set)) v)))
(hash-map)
(for [[k st] m v st] [v k])))
(defn invert-in-sets
"Like `clojure.set/map-invert` but preserves all possible values in sets."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IPersistentMap m]
(invert-in-sets m #{}))
([^clojure.lang.IPersistentMap m ^clojure.lang.PersistentHashSet dst]
(persistent!
(reduce (fn [am [k v]]
(assoc! am v (conj (am v dst) k)))
(transient {}) m))))
(defn map-of-vectors-invert-flatten
"Like `clojure.set/map-invert` but for map of vectors (as values). Duplicated keys
are replaced."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m]
(->> (mapcat (fn [[k v]] (interleave v (repeat k))) m)
(partition 2)
(map vec)
(into {})))
(defn map-values
"Recursively transforms values of a coll using function f. The function should take a
value and return new value."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IFn f, ^clojure.lang.IPersistentMap coll]
(reduce-kv (fn [^clojure.lang.IPersistentMap m, ^clojure.lang.Keyword k, v]
(assoc m k (if (map? v) (map-values f v) (f v))))
(empty coll) coll))
(defn update-values
"Returns the given map with its values identified with keys from vmap updated with
the associated functions from vmap."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IPersistentMap map
^clojure.lang.IPersistentMap vmap]
(update-values map vmap false))
([^clojure.lang.IPersistentMap map
^clojure.lang.IPersistentMap vmap
^Boolean create-keys?]
(if create-keys?
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v]
(update mp k (if (fn? v) v (constantly v))))
map vmap)
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v]
(if (contains? mp k) (update mp k (if (fn? v) v (constantly v))) mp))
map vmap))))
(defn update-values-recur
"Returns the given map with its values identified with keys from vmap recursively
updated with the associated functions from vmap. Shape is not reflected, second
map (vmap) should be flat, searching for keys is recursive, including nested
vectors."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
([^clojure.lang.IPersistentMap map
^clojure.lang.IPersistentMap vmap]
(update-values-recur map vmap false))
([^clojure.lang.IPersistentMap map
^clojure.lang.IPersistentMap vmap
^Boolean create-keys?]
(reduce-kv
(fn [^clojure.lang.IPersistentMap mp k v]
(if (or (map? v) (vector? v))
(assoc mp k (update-values-recur v vmap create-keys?))
mp))
(if (vector? map) map
(update-values map vmap create-keys?)) map)))
(defn ^clojure.lang.IPersistentMap dissoc-in
"Like assoc-in but removes entries. Leaves empty maps."
{:added "1.0.0" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m [k & ks :as keys]]
(if ks
(if-some [nmap (get m k)]
(assoc m k (dissoc-in nmap ks))
m)
(dissoc m k)))
(defn ^clojure.lang.IPersistentMap remove-keys-ns
"Removes namespace component from qualified keys (keywords and
symbols). Non-qualified identifiers and other data types are not renamed."
{:added "1.0.2" :tag clojure.lang.IPersistentMap}
[^clojure.lang.IPersistentMap m]
(map-keys m (fn [k] (if (qualified-ident? k) (name k) k))))