Let's say we a list of maps. Maps all have the same keywords, but we don't know the keywords beforehand.
[{:a 1 :b 2} {:a 3 :b 4}]
And what would be the idiomatic way of merging this list into such a map:
{:a [1 3]
:b [2 4]}
Doesn't seem hard, however as I start to implement the function, it gets super ugly and repetitive. I have a feeling that there are much cleaner ways of achieving this.
Thank you
You can actually get a pretty elegant solution by using several functions from the standard library:
(defn consolidate [& ms]
(apply merge-with conj (zipmap (mapcat keys ms) (repeat [])) ms))
Example:
(consolidate {:a 1 :b 2} {:a 3 :b 4})
;=> {:a [1 3], :b [2 4]}
One cool thing about this solution is that it works even if the maps have different key sets.
i would rather use double reduction to "merge" them with update:
(defn merge-maps-with-vec [maps]
(reduce (partial reduce-kv #(update %1 %2 (fnil conj []) %3))
{} maps))
user> (merge-maps-with-vec [{:a 1 :b 2} {:a 3 :b 4 :c 10}])
{:a [1 3], :b [2 4], :c [10]}
It is not as expressive as #Sam Estep's answer, but on the other hand it doesn't generate any intermediate sequences (like every-key-to-empty-vector map which also needs one extra pass through every entry of every map). Of course, premature optimizations are bad in general, but it won't hurt here i guess. Though the reduce based solution looks a bit more obscure, but being put into a library with proper docs it would not look as obscure to the end user (or to yourself a year after)
While many solutions are possible, here is one that uses some of the convenience functions in the Tupelo library:
(ns clj.core
(:use tupelo.core)
(:require [tupelo.schema :as ts]
[schema.core :as s] ))
(s/defn gather-keys
[list-of-maps :- [ts/KeyMap]]
(newline)
(let [keys-vec (keys (first list-of-maps))]
(s/validate [s/Keyword] keys-vec) ; verify it is a vector of keywords
(apply glue
(for [curr-key keys-vec]
{curr-key (forv [curr-map list-of-maps]
(get curr-map curr-key))} ))))
(deftest t-maps
(spyx
(gather-keys [{:a 1 :b 2}
{:a 3 :b 4} ] )))
(gather-keys [{:a 1, :b 2} {:a 3, :b 4}]) ;=> {:a [1 3], :b [2 4]}
Note that this solution assumes that each input map has an identical set of keys. Normally I'd want to enforce that assumption with a sanity check in the code as well.
Looking at the answer from Sam, I would rewrite it with some temporary variables to help document the sub-steps:
(defn consolidate-keys [list-of-maps]
(let [keys-set (set (mapcat keys list-of-maps))
base-result (zipmap keys-set (repeat [] )) ]
(apply merge-with conj base-result list-of-maps)))
(consolidate-keys [ {:a 1 :b 2}
{:a 3 :z 9} ] )
;=> {:z [9], :b [2], :a [1 3]}
Related
I have a map and I want to write a custom function for updating it.
(-> {:a 1 :b 2}
(fn [x] (update x :a inc)))
This of course is a simple example and could be easily done without the function wrapped around the update, but it shows what I want to do. But this gives me the following error.
Syntax error macroexpanding clojure.core/fn at (core.clj:108:1).
{:a 1, :b 2} - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
{:a 1, :b 2} - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n] spec: :clojure.core.specs.alpha/params+body
I don't get why this is not working, since the threading macro should but my map as first parameter in the function, right?
You can always use macroexpand to see what happened. In your case, macroexpand will return you:
(fn {:a 1, :b 2} [x] (update x :a inc))
obviously this is not a valid function. But if you tweak it this way:
(-> {:a 1 :b 2}
(#(update % :a inc)))
the expanded form will then become valid:
(#(update % :a inc) {:a 1, :b 2})
You don't put a function itself to be called, but call the function without the first parameter, For your example it would be:
> (-> {:a 1 :b 2}
(update :a inc))
{:a 2, :b 2}
This is easier to see by expanding the macro in each case
> (macroexpand-1 '(-> {:a 1 :b 2} (update :a inc)))
(update {:a 1, :b 2} :a inc)
> (macroexpand-1 '(-> {:a 1 :b 2} (fn [x] (update x :a inc))))
(fn {:a 1, :b 2} [x] (update x :a inc))
As #jas and #rmcv pointed out, I was giving the threading macro the function itself, not the call of a function without the argument. So in short terms the solution would be
(-> {:a 1 :b 2}
((fn [x] (update x :a inc))))
I don't think any of these solutions are the simplest. I would propose choosing one of the following:
A. Use the normal threading form:
(-> {:a 1, :b 2}
(update :a inc)) => {:a 2, :b 2}
Everyone is used to seeing this and can understand it easily. Since you have already rejected this approach, I assume you think the code is clearer by using a named parameter.
B. Use a named function
(defn updater [x] (update x :a inc))
(-> {:a 1, :b 2}
updater) => {:a 2, :b 2}
(-> {:a 1, :b 2}
(updater)) => {:a 2, :b 2}
This is more how the -> form was envisioned to work. I think the 2nd version is the clearest, as it is the most consistent where all function expressions have parentheses (single arg or multi-arg).
C. Consider using the it-> macro from the Tupelo Library:
(it-> {:a 1, :b 2}
(update it :a inc)) => {:a 2, :b 2}
Much like the named function, the expression is normal Clojure form without the "invisible" parameter silently inserted into the update expression. The pronoun it serves as the temporary placeholder for the threaded value (an idea copied from Groovy). Simple, explicit, and flexible, since the it can be in the first, last, or any other parameter location:
(it-> 1
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." )
Clojure's (read-string) is really useful.
eg.
(read-string "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
will give me the first object, the {:a 1 :b 2}
But how can I get the rest of string ie. "{:c 3 :d 4} [1 2 3]"
What's the reader equivalent of rest or drop ?
You can wrap the string in a StringReader, then wrap that in a PushbackReader, then read from that reader multiple times.
NB. the example below uses clojure.edn/read, as that is an edn-only reader meant for dealing with pure data; clojure.core/read is primarily meant for reading code and should never be used with untrusted inputs.
(require '[clojure.edn :as edn])
(def s "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
;; Normally one would want to use with-open to close the reader,
;; but here we don't really care and we don't want to accidentally
;; close it before consuming the result:
(let [rdr (java.io.PushbackReader. (java.io.StringReader. s))
sentinel (Object.)] ; ← or just use ::eof as sentinel
(take-while #(not= sentinel %)
(repeatedly #(edn/read {:eof sentinel} rdr))))
;= ({:a 1, :b 2} {:c 3, :d 4} [1 2 3])
ClojureScript version of what should be the accepted answer by https://stackoverflow.com/users/232707/michał-marczyk
(require '[cljs.reader :as rdr])
(require '[cljs.tools.reader.reader-types :as reader-types])
(def s "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
(let [pbr (reader-types/string-push-back-reader s)
sentinel ::eof]
(take-while #(not= sentinel %)
(repeatedly #(rdr/read {:eof sentinel} pbr))))
Probably not very idiomatic but straightforward
(->> (str "(" "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]" ")")
(read-string))
then access to individual elements (you can also use brackets)
If you have a list within the string, you can preserve it via options given to read-string-
(def str-list "({:a 1 :b 2} {:c 3 :d 4} [1 2 3])")
(read-string {:read-cond :preserve} str-list)
;;=> ({:a 1 :b 2} {:c 3 :d 4} [1 2 3])
The source for the available options can be found in the doc string the of read function, i.e. (source read)from the REPL.
I am struggling on how to construct a macro that lets me pass patterns and results to core.match/match in the form of a vector. I would like to be able to do this:
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
> :low
I have tried the below and several other approaches which do not work, unless I pass patterns as a literal.
(defmacro my-match [e ems]
`(m/match [~e] ~#ems))
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
=> CompilerException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(*cider-repl kontrakt*:106:10)
(let [x {:a 1}]
(my-match x [[{:a 2}] :high
[{:a 1}] :low]))
=> :low
Macros are expanded at compile time, so you cannot rely on runtime information (the value of a parameter) during expansion. The root problem is that you can't apply a macro in the same way you can apply a function.
In clojure, how to apply a macro to a list?
So you have to either resort to using eval:
(defmacro functionize [macro]
`(fn [& args#] (eval (cons '~macro args#))))
(defmacro my-match [e ems]
`(apply (functionize m/match) [~e] ~ems))
Or approach the problem in a different way (do runtime pattern matching instead of compile time pattern matching).
The simplest way to solve your problem is with a plain old map:
(ns clj.core
(:use tupelo.core))
(def x {:a 1} )
(def patterns { {:a 2} :high
{:a 1} :low } )
(spyx (get patterns x))
;=> (get patterns x) => :low
Since you have no "wildcard values", you don't need core.match at all. If you would like to match on wild-card values, please see the function wild-match? in the Tupelo library. Samples:
(wild-match? {:a :* :b 2}
{:a 1 :b 2}) ;=> true
(wild-match? [1 :* 3]
[1 2 3]
[1 9 3] )) ;=> true
(wild-match? {:a :* :b 2}
{:a [1 2 3] :b 2}) ;=> true
Input: [{:a "ID1" :b 2} {:a "ID2" :b 4}]
I want to only add up all the keys :b and produce the following:
Result: 6
I thought about doing a filter? to pull all the numbers into vector and add it all up but this seems like doing work twice. I can't use merge-with + here since the :a has a string in it. Do I use a reduce here with a function that will pull the appropriate key?
(reduce (fn [x] (+ (x :b))) 0 list-of-maps)
It would be even nicer if I could retain the map structure with updated value ({:a "ID1" :b 6}) but since I don't really need the other keys, just the total sum is fine.
I want to only add up all the keys :b and produce the following:
Result: 6
I believe workable code is:
(def m1 {:a 1, :b 2})
(def m2 {:a 11, :b 12})
(def m3 {:a 21, :b 22})
(def ms [m1 m2 m3])
(->> ms
(map :b)
(reduce +))
I feel use of ->> here can help readability in your situation.
This says to take action on ms, which is defined to be a vector of maps, threading incremental results through the remaining forms.
The first thing is to transform each entry of maps using the keyword :b as a function on each, extracing the value corresponding to that key, resulting in the sequence:
(2 12 22)
You can then apply reduce exactly as you intuit across that seq to get the result:
user=> (def m1 {:a 1, :b 2})
#'user/m1
user=> (def m2 {:a 11, :b 12})
#'user/m2
user=> (def m3 {:a 21, :b 22})
#'user/m3
user=> (def ms [m1 m2 m3])
#'user/ms
user=> (->> ms
#_=> (map :b)
#_=> (reduce +))
36
I'm a tad confused by what you intend by this part of the question:
It would be even nicer if I could retain the map structure with updated value ({:a "ID1" :b 6})
Do you want to have each value for :b across all maps contain the sum of them all in a result, or something else?
(reduce + (map :b list-of-maps))
This its simple but it works!
user=> (+ (your-map :b) (your-map :b))
or
user=> (def x [{:a "ID1" :b 2} {:a "ID2" :b 4}])
#'user/x
user=> (+ ((first x) :b) ((second x) :b))
6
user=>
or user=> (+ ((nth x 0) :b) ((nth x 1) :b))
6
Destructuring a map looks reversed to me. Can anybody explain what is happening?
I expect that this is the right form of destructuring a map
;=> (let [{:a a :b b} {:a 1 :b 2}] [a b])
which returns Exception Unsupported binding form: :a clojure.core/destructure/pb--4541 (core.clj:4029). Clojure documentations say that below is the right way. But it looks that keys and values are reversed.
This should be the right way:
;=> (let [{a :a b :b} {:a 1 :b 2}] [a b])
[1 2]
What is happening when destructuring a map?
It is not really reversed, actually it makes sense. It says: bind to symbol 'a' to value that is associated with the keyword :a
Are you aware of this when your map uses keywords as keys?
(let [{:keys [a b]} {:a 1 :b 2}] [a b])
Much neater and elegant!
Other variants exist if your keys are symbols or strings.
Also, it makes it possible to distinguish between these to cases:
cljs.user=> (let [{foo :foo :as bar} {:foo 3 :as 4}] [foo bar])
[3 {:foo 3, :as 4}]
cljs.user=> (let [{foo :foo bar :as} {:foo 3 :as 4}] [foo bar])
[3 4]