Best way to update several values in hashmap? - clojure

I have a hash map like this:
{:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg" :do-not-split "abcdefg hijk"}
And I'd like to split some of the strings to get vectors:
; expected result
{:key1 ["aaa" "bbb" "ccc"] :key2 ["ddd" "eee"] :key3 ["fff" "ggg"] :do-not-split "abcdefg hijk"}
I use update-in three times now like the following but it seems ugly.
(-> my-hash (update-in [:key1] #(split % #"\s"))
(update-in [:key2] #(split % #"\s"))
(update-in [:key3] #(split % #"\s")))
I hope there's sth like (update-all my-hash [:key1 :key2 :key3] fn)

You can use reduce:
user=> (def my-hash {:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg"})
#'user/my-hash
user=> (defn split-it [s] (clojure.string/split s #"\s"))
#'user/split-it
user=> (reduce #(update-in %1 [%2] split-it) my-hash [:key1 :key2 :key3])
{:key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}

Just map the values based on a function that makes the decision about whether to split or not.
user=> (def x {:key1 "aaa bbb ccc"
:key2 "ddd eee"
:key3 "fff ggg"
:do-not-split "abcdefg hijk"})
#'user/x
user=> (defn split-some [predicate [key value]]
(if (predicate key)
[key (str/split value #" ")]
[key value]))
#'user/split-some
user=> (into {} (map #(split-some #{:key1 :key2 :key3} %) x))
{:do-not-split "abcdefg hijk", :key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}

This is a different way of approaching the problem.
Think about it for a second: if your string were in a list, how would you approach it?
The answer is that you would use map to get a list of vectors:
(map #(split % #"\s") list-of-strings)
If you think harder you would arrive at the conclusion that what you really want is to map a function over the values of a map. Obviously map doesn't work here as it works for sequences only.
However, is there a generic version of map? It turns out there is! It's called fmap and comes from the concept of functors which you can ignore for now. This is how you would use it:
(fmap my-hash #(split % #"\s"))
See how the intent is a lot clearer now?
The only drawback is that fmap isn't a core function but it is available through the algo.generic library.
Of course if including a new library feels like too much at this stage, you can always steel the source code - and attribute to its author - from the library itself in this link:
(into (empty my-hash) (for [[k v] my-hash] [k (your-function-here v)]))

Related

Using clojure, Is there a better way to to remove a item from a sequence, which is the value in a map?

There is a map containing sequences. The sequences contain items.
I want to remove a given item from any sequence that contains it.
The solution I found does what it should, but I wonder if there is a better
or more elegant way to achieve the same.
my current solution:
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
The test describe the expected behaviour:
(require '[clojure.test :as t])
(def my-map {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]})
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
(t/is (= (remove-item-from-map-value my-map "unknown-item") my-map))
(t/is (= (remove-item-from-map-value my-map "itemFive") {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemThree") {:keyOne ["itemOne"]
:keyTwo ["itemTwo"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemOne") {:keyOne []
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
I'm fairly new to clojure and am interested in different solutions.
So any input is welcome.
I throw in the specter
version for good measure. It keeps the vectors inside the map
and it's really compact.
(setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
Example
user=> (use 'com.rpl.specter)
nil
user=> (def my-map {:keyOne ["itemOne"]
#_=> :keyTwo ["itemTwo" "itemThree"]
#_=> :keyThree ["itemFour" "itemFive" "itemSix"]})
#_=>
#'user/my-map
user=> (setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
{:keyOne ["itemOne"],
:keyThree ["itemFour" "itemSix"],
:keyTwo ["itemTwo" "itemThree"]}
user=> (setval [MAP-VALS ALL #{"unknown"}] NONE my-map)
{:keyOne ["itemOne"],
:keyThree ["itemFour" "itemFive" "itemSix"],
:keyTwo ["itemTwo" "itemThree"]}
i would go with something like this:
user> (defn remove-item [my-map item]
(into {}
(map (fn [[k v]] [k (remove #{item} v)]))
my-map))
#'user/remove-item
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),
;; :keyTwo ("itemTwo" "itemThree"),
;; :keyThree ("itemFive" "itemSix")}
you could also make up a handy function map-val performing mapping on map values:
(defn map-val [f data]
(reduce-kv
(fn [acc k v] (assoc acc k (f v)))
{} data))
or shortly like this:
(defn map-val [f data]
(reduce #(update % %2 f) data (keys data)))
user> (map-val inc {:a 1 :b 2})
;;=> {:a 2, :b 3}
(defn remove-item [my-map item]
(map-val (partial remove #{item}) my-map))
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),
;; :keyTwo ("itemTwo" "itemThree"),
;; :keyThree ("itemFive" "itemSix")}
I think your solution is mostly okay, but I would try to avoid the apply merge part, as you can easily recreate a map from a sequence with into. Also, you could also use map instead of for which I think is a little bit more idiomatic in this case as you don't use any of the list comprehension features of for.
(defn remove-item-from-map-value [m item]
(->> m
(map (fn [[k vs]]
{k (remove #(= item %) vs)}))
(into {})))
Another solution much like #leetwinski:
(defn remove-item [m i]
(zipmap (keys m)
(map (fn [v] (remove #(= % i) v))
(vals m))))
Here's a one-liner which does this in an elegant way. The perfect function for me to use in this scenario is clojure.walk/prewalk. What this fn does is it traverse all of the sub-forms of the form that you pass to it and it transforms them with the provided fn:
(defn remove-item-from-map-value [data item]
(clojure.walk/prewalk #(if (map-entry? %) [(first %) (remove #{item} (second %))] %) data))
What the remove-item-from-map-value fn will do is it will check if current form is a map entry and if so, it will remove specified key from its value (second element of the map entry, which is a vector containing a key and a value, respectively).
The best this about this approach is that is is completely extendable: you could decide to do different things for different types of forms, you can also handle nested forms, etc.
It took me some time to master this fn but once I got it I found it extremely useful!

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.

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

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))

Enumerate over a sequence in Clojure?

In Python I can do this:
animals = ['dog', 'cat', 'bird']
for i, animal in enumerate(animals):
print i, animal
Which outputs:
0 dog
1 cat
2 bird
How would I accomplish the same thing in Clojure? I considered using a list comprehension like this:
(println
(let [animals ["dog" "cat" "bird"]]
(for [i (range (count animals))
animal animals]
(format "%d %d\n" i animal))))
But this prints out every combination of number and animal. I'm guessing there is a simple and elegant way to do this but I'm not seeing it.
There is map-indexed in core as of 1.2.
Your example would be:
(doseq [[i animal] (map-indexed vector ["dog" "cat" "bird"])]
(println i animal))
Quick solution:
(let [animals ["dog", "cat", "bird"]]
(map vector (range) animals))
Or, if you want to wrap it in a function:
(defn enum [s]
(map vector (range) s))
(doseq [[i animal] (enum ["dog", "cat", "bird"])]
(println i animal))
What happens here is the function vector is applied to each element in both sequences, and the result is collected in a lazy collection.
Go ahead, try it in your repl.
Use indexed from clojure.contrib.seq:
Usage: (indexed s)
Returns a lazy sequence of [index, item] pairs, where items come
from 's' and indexes count up from zero.
(indexed '(a b c d)) => ([0 a] [1 b] [2 c] [3 d]
For your example this is
(require 'clojure.contrib.seq)
(doseq [[i animal] (clojure.contrib.seq/indexed ["dog", "cat", "bird"])]
(println i animal))
map-indexed looks right but: Do we really need all of the doseq and deconstructing args stuff in the other answers?
(map-indexed println ["dog", "cat", "bird"])
EDIT:
as noted by #gits this works in the REPL but doesn't respect that clojure is lazy by default. dorun seems to be the closest among doseq, doall and doseq for this. doseq, howver, seems to be the idiomatic favorite here.
(dorun (map-indexed println ["dog", "cat", "bird"]))
Yet another option is to use reduce-kv, which pairs the elements of a vector with their indexes.
Thus,
(reduce-kv #(println %2 %3) nil ["dog" "cat" "bird"])
or perhaps the slightly more explicit
(reduce-kv (fn [_ i animal] (println i animal)) nil ["dog" "cat" "bird"])
I wouldn’t pick this solution over the one with doseq here, but it is good to be aware of this specialisation for vectors in reduce-kv.

Clojure - named arguments

Does Clojure have named arguments? If so, can you please provide a small example of it?
In Clojure 1.2, you can destructure the rest argument just like you would destructure a map. This means you can do named non-positional keyword arguments. Here is an example:
user> (defn blah [& {:keys [key1 key2 key3]}] (str key1 key2 key3))
#'user/blah
user> (blah :key1 "Hai" :key2 " there" :key3 10)
"Hai there10"
user> (blah :key1 "Hai" :key2 " there")
"Hai there"
user> (defn blah [& {:keys [key1 key2 key3] :as everything}] everything)
#'user/blah
user> (blah :key1 "Hai" :key2 " there")
{:key2 " there", :key1 "Hai"}
Anything you can do while destructuring a Clojure map can be done in a function's argument list as shown above. Including using :or to define defaults for the arguments like this:
user> (defn blah [& {:keys [key1 key2 key3] :or {key3 10}}] (str key1 key2 key3))
#'user/blah
user> (blah :key1 "Hai" :key2 " there")
"Hai there10"
But this is in Clojure 1.2. Alternatively, in older versions, you can do this to simulate the same thing:
user> (defn blah [& rest] (let [{:keys [key1 key2 key3] :or {key3 10}} (apply hash-map rest)] (str key1 key2 key3)))
#'user/blah
user> (blah :key1 "Hai" :key2 " there")
"Hai there10"
and that works generally the same way.
And you can also have positional arguments that come before the keyword arguments:
user> (defn blah [x y & {:keys [key1 key2 key3] :or {key3 10}}] (str x y key1 key2 key3))
#'user/blah
user> (blah "x" "Y" :key1 "Hai" :key2 " there")
"xYHai there10"
These are not optional and have to be provided.
You can actually destructure the rest argument like you would any Clojure collection.
user> (defn blah [& [one two & more]] (str one two "and the rest: " more))
#'user/blah
user> (blah 1 2 "ressssssst")
"12and the rest: (\"ressssssst\")"
You can do this sort of thing even in Clojure 1.1. The map-style destructuring for keyword arguments only came in 1.2 though.
In addition to Raynes' excellent answer, there is also a macro in clojure-contrib that makes life easier:
user=> (use '[clojure.contrib.def :only [defnk]])
nil
user=> (defnk foo [a b :c 8 :d 9]
[a b c d])
#'user/foo
user=> (foo 1 2)
[1 2 8 9]
user=> (foo 1 2 3)
java.lang.IllegalArgumentException: No value supplied for key: 3 (NO_SOURCE_FILE:0)
user=> (foo 1 2 :c 3)
[1 2 3 9]
As of Clojure version 1.8, keyword support still seems a bit meh.
You can specify keyword arguments like this:
(defn myfn1
"Specifying keyword arguments without default values"
[& {:keys [arg1 arg2]}]
(list arg1 arg2))
Examples of calling it:
(myfn1 :arg1 23 :arg2 45) --> evaluates to (23 45)
(myfn1 :arg1 22) --> evaluates to (22 nil)
If you want to specify default values for these keyword arguments:
(defn myfn2
"Another version, this time with default values specified"
[& {:keys [arg1 arg2] :or {arg1 45 arg2 55}}]
(list arg1 arg2))
This does the expected thing in the second case:
(myfn2 :arg1 22) --> evaluates to (22 55)
There are pros and cons to each part of each language, but just for comparison, this is how you would do the same stuff in Common Lisp:
(defun myfn3
(&key arg1 arg2)
"Look Ma, keyword args!"
(list arg1 arg2))
(defun myfn4
(&key (arg1 45) (arg2 55))
"Once again, with default values"
(list arg1 arg2))
Do you perhaps mean named parameters? These aren't directly available, but you can use this vectors approach if you like, which may give you what you want.
At RosettaCode there's a deeper explanation on how to do this using destructuring.