Putting key-values into map conditionally, what are the concise ways? - clojure

What are the concise/ elegant ways to put into a map key-value pairs for which the corresponding conditions are true?
That is to translate
[condition1 condition2 ...] [key1 val1 key2 val2 ...]
or
[condition1 condition2 ...] [key1 key2 ...] [val1 val2 ...]
or
[condition1 key1 val1 condition2 key2 val2...]
into
{key-for-true-condition1 val-for-true-condition1, key-for-true-condition2 val-for-true-condition2...}
I think to use "reduce" with "if" in its lambda but interested in more concise/ beautiful/ elegant/ idiomatic ways.

(into {} (for [[c k v] (partition 3 coll) :when c]
[k v]))
Based on the 'for'-Version from Kintaro but a little shorter.

To be honest IMO, the version with reduce and if are already the most elegant and idiomatic see comment from nickik below.
(def coll [true :a "v1" false :b "v2" true :c "v3"])
(reduce (fn [a [c k v]] (if c (assoc a k v) a)) {} (partition 3 coll))
Here is a version using the for comprehension for the third case:
(apply array-map (flatten (for [[c k v] (partition 3 coll) :when c]
[k v])))
Edit:
For the second case you convert it to the third case by doing:
(def c [true false true])
(def k [:a :b :c])
(def v ["v1" "v2" "v3"])
(def coll (interleave c k v))
But I think the map version from nickik is better here.

I would first think about it as how to be best map your functional operations over a stream:
Group condition/key/value into a chunk
Filter chunks where the condition is not true
Drop the conditions
Flatten the chunks
Create a map from the result
Which looks like:
(def coll [true :a "v1" false :b "v2" true :c "v3"])
(apply hash-map
(flatten
(map #(drop 1 %)
(filter #(first %)
(partition 3 coll)))))
Or if you're feeling thready:
(->> coll
(partition 3)
(filter #(first %))
(map #(drop 1 %))
flatten
(apply hash-map))
I'm not sure this is elegant or concise, but I think it's easy to read. Note that if you frequently deal with data in this shape, you may find that steps like (partition 3 coll) or (first %) might be useful reusable functions in their own right leading to something like:
(defn condition-group [coll] (partition 3 coll))
(defn condition [group] (first group))
(defn but-condition [group] (drop 1 group))
(defn kv-map [kv-pairs] (apply hash-map (flatten kv-pairs)))
(->> coll
condition-group
(filter condition)
(map but-condition)
kv-map)

(def coll [true :key1 "value1" false :key2 "value2" true :key3 "value3"])
(defn testpair [[cond key val]]
(when cond
{key val}))
(apply merge (map testpair (partition 3 coll)))
=> {:key3 "value3", :key1 "value1"}
This would be one way but if you want other combinations of condition key and value you have to change the code. You didn't mention witch one would be best.
Edit:
Because its the first on your list
(def conditions [true false true] )
(def keyval [:key1 "value1" :key2 "value2" :key3 "value3"])
(defn testpair [cond [key val]]
(when cond
{key val}))
(apply merge (map testpair conditions (partition 2 keyval)))
Because its fun :)
(def conditions [true false true] )
(def keys [:key1 :key2 :key3])
(def vals ["value1" "value1" "value3"])
(defn testpair [cond key val]
(when cond
{key val}))
(apply merge (map testpair conditions keys vals))

Related

Pretty-print Nested Hash-map in ClojureScript

Is there a convenient way in ClojureScript to pretty print a nested hash-map in the way that the whole tree-structure becomes immediately visible.
For instance a map like this
(def my-map {:a {:b 1 :c 9} :b {:d 8 :e {:f 2 :g 3 :h 4}} :c 10})
should be printed like this:
{:a {:b 1
:c 9}
:b {:d 8
:e {:f 2
:g 3
:h 4}}
:c 10}
EDIT: There might also be vectors in the map. The usecase is just to inspect larger data structures during development.
There is no built-in way to do it. You might come close to what you want by using cljs.pprint and setting cljs.pprint/*print-right-margin* to a low value.
I would recommend to take a look at a small library shodan which provides a very useful inspect function:
(require '[shodan.inspection :refer [inspect]])
(inspect {:aaaaaa 1
:bbbbbb {:ccc 2
:dddddd [1 2 3 4 5]}})
It won't print anything in your CLJS REPL but will provide a handy view in your browser's console:
You can collapse and expand nested datastructures - it basically does what you asked for.
As a personal challenge I wrote the following code:
(enable-console-print!)
(def atomic? (complement coll?))
(def padding #(apply str (repeat % " ")))
(def tabulate #(apply str (repeat % "\t")))
(def strcat #(->> (apply concat %&) (apply str)))
(defn my-max-key [x] (if (empty? x) [""] (apply (partial max-key count) x)))
(defn longest-key [m] (->> m keys (filter atomic?) (map str) my-max-key))
(def length (comp count str))
(def not-map? (complement map?))
(def nested? #(some coll? %))
(def join #(apply str (interpose % %2)))
(def join-lines (partial join "\n"))
(defn has-atomic? [coll] (some atomic? coll))
(defn diff-key-lengths [key1 key2] (- (length key1) (length key2)))
(defn convert
([thing] (convert -1 thing))
([depth thing]
(defn convert-items []
(defn convert-seq []
(conj []
(map (partial convert (inc depth)) thing)
""))
(defn string-horizontally [[key value]]
(str (tabulate (inc depth))
key
(padding (diff-key-lengths (longest-key thing) key))
" → "
value))
(defn string-vertically [[key value]]
(str (convert (inc depth) key) "\n"
(convert (+ 2 depth) "↓") "\n"
(convert (inc depth) value) "\n"))
(defn convert-kv [[key value]]
(if (nested? [key value])
(string-vertically [key value])
(string-horizontally [key value])))
(cond (atomic? thing)
[(str (tabulate depth) thing)]
(not-map? thing)
(convert-seq)
(map? thing)
(map convert-kv thing)))
(->> (convert-items) flatten join-lines)))
(def sample-input [["the first thing in this nested vector"]
{{"this is a key in a nested map"
"that points to me!!!"}
{"and that entire map points to this map!!!"
"cool!!!"
"but it gets cooler cause..."
"the value's line up!!!"}}])
(->> sample-input convert println)
The terminal output is (psst... the values in a map do line up but I don't think that chrome uses a monospaced font!):

clojure find arbitrarily nested key

Is there an easy way in Clojure (maybe using specter) to filter collections depending on whether the an arbitrarily nested key with a known name contains an element ?
Ex. :
(def coll [{:res [{:a [{:thekey [
"the value I am looking for"
...
]
}
]}
{:res ...}
{:res ...}
]}])
Knowing that :a could have a different name, and that :thekey could be nested somewhere else.
Let's say I would like to do :
#(find-nested :thekey #{"the value I am looking for"} coll) ;; returns a vector containing the first element in coll (and maybe others)
use zippers.
in repl:
user> coll
[{:res [{:a [{:thekey ["the value I am looking for"]}]} {:res 1} {:res 1}]}]
user> (require '[clojure.zip :as z])
nil
user> (def cc (z/zipper coll? seq nil coll))
#'user/cc
user> (loop [x cc]
(if (= (z/node x) :thekey)
(z/node (z/next x))
(recur (z/next x))))
["the value I am looking for"]
update:
this version is flawed, since it doesn't care about :thekey being the key in a map, or just keyword in a vector, so it would give unneeded result for coll [[:thekey [1 2 3]]]. Here is an updated version:
(defn lookup-key [k coll]
(let [coll-zip (z/zipper coll? #(if (map? %) (vals %) %) nil coll)]
(loop [x coll-zip]
(when-not (z/end? x)
(if-let [v (-> x z/node k)] v (recur (z/next x)))))))
in repl:
user> (lookup-key :thekey coll)
["the value I am looking for"]
user> (lookup-key :absent coll)
nil
lets say we have the same keyword somewhere in a vector in a coll:
(def coll [{:res [:thekey
{:a [{:thekey ["the value I am looking for"]}]}
{:res 1} {:res 1}]}])
#'user/coll
user> (lookup-key :thekey coll)
["the value I am looking for"]
which is what we need.

Find Value of Specific Key in Nested Map

In Clojure, how can I find the value of a key that may be deep in a nested map structure? For example:
(def m {:a {:b "b"
:c "c"
:d {:e "e"
:f "f"}}})
(find-nested m :f)
=> "f"
Clojure offers tree-seq to do a depth-first traversal of any value. This will simplify the logic needed to find your nested key:
(defn find-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(some k)))
(find-nested {:a {:b {:c 1}, :d 2}} :c)
;; => 1
Also, finding all matches becomes a matter of replacing some with keep:
(defn find-all-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(keep k)))
(find-all-nested {:a {:b {:c 1}, :c 2}} :c)
;; => [2 1]
Note that maps with nil values might require some special treatment.
Update: If you look at the code above, you can see that k can actually be a function which offers a lot more possibilities:
to find a string key:
(find-nested m #(get % "k"))
to find multiple keys:
(find-nested m #(some % [:a :b]))
to find only positive values in maps of integers:
(find-nested m #(when (some-> % :k pos?) (:k %)))
If you know the nested path then use get-in.
=> (get-in m [:a :d :f])
=> "f"
See here for details: https://clojuredocs.org/clojure.core/get-in
If you don't know the path in your nested structure you could write a function that recurses through the nested map looking for the particular key in question and either returns its value when it finds the first one or returns all the values for :f in a seq.
If you know the "path", consider using get-in:
(get-in m [:a :d :f]) ; => "f"
If the "path" is unknown you can use something like next function:
(defn find-in [m k]
(if (map? m)
(let [v (m k)]
(->> m
vals
(map #(find-in % k)) ; Search in "child" maps
(cons v) ; Add result from current level
(filter (complement nil?))
first))))
(find-in m :f) ; "f"
(find-in m :d) ; {:e "e", :f "f"}
Note: given function will find only the first occurrence.
Here is a version that will find the key without knowing the path to it. If there are multiple matching keys, only one will be returned:
(defn find-key [m k]
(loop [m' m]
(when (seq m')
(if-let [v (get m' k)]
v
(recur (reduce merge
(map (fn [[_ v]]
(when (map? v) v))
m')))))))
If you require all values you can use:
(defn merge-map-vals [m]
(reduce (partial merge-with vector)
(map (fn [[_ v]]
(when (map? v) v))
m)))
(defn find-key [m k]
(flatten
(nfirst
(drop-while first
(iterate (fn [[m' acc]]
(if (seq m')
(if-let [v (get m' k)]
[(merge-map-vals m') (conj acc v)]
[(merge-map-vals m') acc])
[nil acc]))
[m []])))))

Apply defaults to a map

I'm looking for a way to apply some defaults to map. I know the following works:
(defn apply-defaults
[needing-defaults]
(merge {:key1 (fn1 10)
:key2 (fn2 76)}
needing-defaults))
The issue with the above is that the value of fn1 and fn2 are evaluated even though needing-defaults might already have these keys - thus never needing them.
I've tried with merge-with but that doesn't seem to work. I'm quite new at this - any suggestions?
I'm ussually applying defaults with merge-with function:
(merge-with #(or %1 %2) my-map default-map)
But in your case it should be something like:
(reduce (fn [m [k v]]
(if (contains? m k) m (assoc m k (v))))
needing-defaults
defaults)
where defaults is a map of functions:
{ :key1 #(fn1 10)
:key2 #(fn2 76)}
if is a special form, so it newer evaluates its false branch.
See my example for more info.
If I understand your question correctly, how about this?
(defn apply-defaults [nd]
(into {:key1 (sf1 10) :key2 (sf2 76)} nd))
You could use a macro to generate the contains? checks and short circuit the function calls.
(defmacro merge-with-defaults [default-coll coll]
(let [ks (reduce (fn [a k] (conj a
`(not (contains? ~coll ~k))
`(assoc ~k ~(k default-coll))))
[] (keys default-coll))]
`(cond-> ~coll ~#ks)))
(defn apply-defaults [needing-defaults]
(merge-with-defaults {:key1 (fn1 10)
:key2 (fn2 76)}
needing-defaults))
Just remember to keep the function calls inside the call to merge-with-defaults to prevent evaluation.
Since you can merge nil into a map, you can use the if-not macro:
(merge {} nil {:a 1} nil) ;; {:a 1}
Try this:
(defn apply-defaults [col]
(merge col
(if-not (contains? col :key1) {:key1 (some-function1 10)})
(if-not (contains? col :key2) {:key2 (some-function2 76)})))
some-function1 and some-function2 will only be executed when col does not already have the key.

Clojure Multi Maps

Very simple + silly question:
Does clojure provide multi maps? I currently have something like this:
(defn wrap [func]
(fn [mp x]
(let [k (func x)]
(assoc mp k
(match (get mp k)
nil [x]
v (cons v x))))))
(defn create-mm [func lst]
(reduce (wrap func) {} lst))
Which ends up creating a map, where for each key, we have a vector of all elements with that key. However, it seems like multi maps is a very basic data structure, and I wonder if clojure has it built-in.
Thanks
I don't think this is really necessary as a distinct type, as Clojure's flexibility allow you to quickly make your own by just using maps and sets. See here:
http://paste.lisp.org/display/89840
Edit (I should have just pasted this in since it's so small)
Example Code (Courtesy Stuart Sierra)
(ns #^{:doc "A multimap is a map that permits multiple values for each
key. In Clojure we can represent a multimap as a map with sets as
values."}
multimap
(:use [clojure.set :only (union)]))
(defn add
"Adds key-value pairs the multimap."
([mm k v]
(assoc mm k (conj (get mm k #{}) v)))
([mm k v & kvs]
(apply add (add mm k v) kvs)))
(defn del
"Removes key-value pairs from the multimap."
([mm k v]
(let [mmv (disj (get mm k) v)]
(if (seq mmv)
(assoc mm k mmv)
(dissoc mm k))))
([mm k v & kvs]
(apply del (del mm k v) kvs)))
(defn mm-merge
"Merges the multimaps, taking the union of values."
[& mms]
(apply (partial merge-with union) mms))
(comment
(def mm (add {} :foo 1 :foo 2 :foo 3))
;; mm == {:foo #{1 2 3}}
(mm-merge mm (add {} :foo 4 :bar 2))
;;=> {:bar #{2}, :foo #{1 2 3 4}}
(del mm :foo 2)
;;=> {:foo #{1 3}}
)
Extra test for the case pointed out in the comments:
(comment
(-> {} (add :a 1) (del :a 1) (contains? :a))
;;=> false
)