Atomic and conditional map update - clojure

I would like to update a simple atom, like: {:a 1} and return new value just like swap! does, only if given key already exists (and return nil otherwise).
This is the simplest solution I figured out:
(defn cond-assoc [store k v]
(when (get #store k)
(swap! store assoc k v)))
but I don't feel it's really atomic. In the wild lot of things may happen between (get #store k) and the swap! operations. Is there any better solution than that?

You are right, your solution is not very atomic. To make an atomic swap, make a simple function that transforms the map only when required:
(defn assoc-if-exists [m k v]
(if (m k) (assoc m k v) m))
Note that this has nothing to do with an atom or with being atomic. To use it with an atom, simply do
(def store (atom {:a 1}))
(swap! store assoc-if-exists :a 4) ;; {:a 4}, value changed
(swap! store assoc-if-exists :b 4) ;; {:a 4}, nothing added
Notice that this does not return nil when the key does not exists. If you want that behavior, you could check if the returned value contains the key:
(defn atomic-assoc-or-nil [a k v]
(let [r (swap! a assoc-if-exists k v)]
(when (get r k) r)))

Related

Get key by first element in value list in Clojure

This is similar to Clojure get map key by value
However, there is one difference. How would you do the same thing if hm is like
{1 ["bar" "choco"]}
The idea being to get 1 (the key) where the first element if the value list is "bar"? Please feel free to close/merge this question if some other question answers it.
I tried something like this, but it doesn't work.
(def hm {:foo ["bar", "choco"]})
(keep #(when (= ((nth val 0) %) "bar")
(key %))
hm)
You can filter the map and return the first element of the first item in the resulting sequence:
(ffirst (filter (fn [[k [v & _]]] (= "bar" v)) hm))
you can destructure the vector value to access the second and/or third elements e.g.
(ffirst (filter (fn [[k [f s t & _]]] (= "choco" s))
{:foo ["bar", "choco"]}))
past the first few elements you will probably find nth more readable.
Another way to do it using some:
(some (fn [[k [v & _]]] (when (= "bar" v) k)) hm)
Your example was pretty close to working, with some minor changes:
(keep #(when (= (nth (val %) 0) "bar")
(key %))
hm)
keep and some are similar, but some only returns one result.
in addition to all the above (correct) answers, you could also want to reindex your map to desired form, especially if the search operation is called quite frequently and the the initial map is rather big, this would allow you to decrease the search complexity from linear to constant:
(defn map-invert+ [kfn vfn data]
(reduce (fn [acc entry] (assoc acc (kfn entry) (vfn entry)))
{} data))
user> (def data
{1 ["bar" "choco"]
2 ["some" "thing"]})
#'user/data
user> (def inverted (map-invert+ (comp first val) key data))
#'user/inverted
user> inverted
;;=> {"bar" 1, "some" 2}
user> (inverted "bar")
;;=> 1

Return a map from within a Clojure function

I've been learning Clojure for a few weeks now. I know the basics of the data structures and some functions. (I'm reading the Clojure Programming book).
I'm stuck with the following. I'm writing a function which will lower case the keys of the supplied map.
(defn lower-case-map [m]
(def lm {})
(doseq [k (keys m)]
(assoc lm (str/lower-case k) (m k))))
This does what I want, but how do I return the map? Is the def correct?
I know this works
(defn lower-case-map [m]
(assoc {} :a 1))
But the doseq above seems to be creating a problem.
Within a function body you should define your local variables with let, yet this code looks alot like you try to bend it into an imperative mindset (def tempvar = new Map; foreach k,v in m do tempvar[k.toLower] = v; return tempvar). Also note, that the docs of doseq explicitly state, that it returns nil.
The functional approach would be a map or reduce over the input returning the result directly. E.g. a simple approach to map (iterating the sequence of elements, destructure the key/value tuple, emit a modified tuple, turn them back into a map):
user=> (into {} (map (fn [[k v]] [(.toLowerCase k) v]) {"A" 1 "B" 2}))
{"a" 1, "b" 2}
For your use-case (modify all keys in a map) is already a nice core function: reduce-kv:
user=> (doc reduce-kv)
-------------------------
clojure.core/reduce-kv
([f init coll])
Reduces an associative collection. f should be a function of 3
arguments. Returns the result of applying f to init, the first key
and the first value in coll, then applying f to that result and the
2nd key and value, etc. If coll contains no entries, returns init
and f is not called. Note that reduce-kv is supported on vectors,
where the keys will be the ordinals.
user=> (reduce-kv (fn [m k v] (assoc m (.toLowerCase k) v)) {} {"A" 1 "B" 2})
{"a" 1, "b" 2}

Clojure creating a flat map with for

I'm working my way through 4clojure and I'm stuck on Problem 156 (Map Defaults).
I can't figure out why the function bellow doesn't return a flat map
((fn [d k] (for [i k :let [r {}]]
(conj r [i d])))
[:a :b] [:foo :bar])
Current result is ({:foo [:a :b]} {:bar [:a :b]})
But I expected {:foo [:a :b], :bar [:a :b]}
Inside for, r is created anew in every iteration, gets populated with [i d] and gets yielded as an element of the lazy sequence. As a result, you obtain this sequence whose elements are small one-entry maps.
What you need is reduce. It loops over a sequence updating the accumulator using a function you provide:
(defn fun1 [d k]
(reduce
(fn [acc i] (conj acc [i d]))
{}
k))
It starts from an empty map, and for every element i in k it calls the lambda, which adds an entry to the map (passed to the lambda as acc). The result is one big map with all these entries.
Alternatively, you could just generate the key/value pairs with your for expression, and then use the into function to shove them all in a map:
((fn [d k] (into {} (for [i k] [i d])))
[:a :b] [:foo :bar])
; => {:foo [:a :b], :bar [:a :b]}
For those coming here looking for a flatmap function in Clojure, check out mapcat:
Returns the result of applying concat to the result of applying map to
f and colls.

Dissoc multiple descendent keys of a map?

How can I search and dissoc multiple descendent keys.
Example:
(def d {:foo 123
:bar {
:baz 456
:bam {
:whiz 789}}})
(dissoc-descendents d [:foo :bam])
;->> {:bar {:baz 456}}
clojure.walk is useful in this kind of situations:
(use 'clojure.walk)
(postwalk #(if (map? %) (dissoc % :foo :bam) %) d)
If you wanted to implement it directly then I'd suggest something like this:
(defn dissoc-descendents [coll descendents]
(let [descendents (if (set? descendents) descendents (set descendents))]
(if (associative? coll)
(reduce
(fn [m [k v]] (if (descendents k)
(dissoc m k)
(let [new-val (dissoc-descendents v descendents)]
(if (identical? new-val v) m (assoc m k new-val)))))
coll
coll)
coll)))
Key things to note about the implementation:
It makes sense to convert descendents into a set: this will allow quick membership tests if the set of keys to remove is large
There is some logic to ensure that if a value doesn't change, you don't need to alter that part of the map. This is quite a big performance win if large areas of the map are unchanged.

defn vs. let with regards to decomposition

I define a function, which takes two parameters - map and a key. The key is referenced from the map parameter decomposition
(defn myfunc [{v k} k]
v)
when I call:
(myfunc {:a 10} :a)
It surprisingly produces expected result: 10
Similar thing in the let:
(let [{v k} {:a 10} k :a] v)
fails, because k is not defined at the moment, when first part is evaluated.
My question is: why decomposition inside function parameters behaves differently compared to decomposition in let expressions?
Macroexpanding the defn form I get the equivalent of this (removed .withMeta stuff and reformatted):
(def myfunc
(fn* myfunc
([p__11393 k]
(let* [map__11394 p__11393
map__11394 (if (seq? map__11394)
(apply hash-map map__11394)
map__11394)
v (get map__11394 k)]
v))))
So here we can see that the {v k} map is in fact first assigned to a local variable p__11393. Then the if statement tests if that variable is in fact a sequence and turns it into a hash-map if so, otherwise leaves it as it is. Importantly, the value assigned to k is looked up in the map after all of this happens, thus this works without error (and also would if :a wasn't in the map, get returns nil in that case).
On the other hand macroexpanding the let form I get
(let*
[map__11431
{:a 10}
map__11431
(if (seq? map__11431) (apply hash-map map__11431) map__11431)
v
(get map__11431 k)
k
:a]
v)
and here we can see that v gets the result of (get map__11431 k), but k isn't defined at this point yet, hence the error.
This isn't a complete answer, but the following does work:
user=> (defn myfunc [{v k} k] v)
#'user/myfunc
user=> (myfunc {:a 10} :a)
10
user=> (let [k :a {v k} {:a 10}] v)
10
user=>