Replace vector inside of map - clojure

I have a vector of maps which looks like this
[{:key1 val1 :key2 val2 :key3 [vector]}
{:key1 val1 :key2 val2 :key3 [vector]}]
In other words, a vector of n maps, with one of the keys also being a vector.
I want to replace the :key3 vector with a new vector, in all the n maps in the outer vector, but I can't figure out a good way to do this.
As a bonus question, I also want to create a new map with the same structure, but with the inner vector containing a chosen range of elements. So for example from this
[{:key1 val1 :key2 val2 :key3 [v1 v2 v3 v4]}
{:key1 val2 :key2 val2 :key3 [v1 v2 v3 v4]}]
to this:
[{:key1 val1 :key2 val2 :key3 [v2 v3]}
{:key1 val2 :key2 val2 :key3 [v2 v3]}]
In other words picking out a range of elements from the internal vector while keeping the data structure otherwise intact.

You can use mapv to map over the elements of the vector, update :key3 using update-in then do another mapv on the vector to extract the indices you want:
(def m [{:key1 val1 :key2 val2 :key3 [:a :b :c :d]}
{:key1 val1 :key2 val2 :key3 [:a :b :c :d]}])
(mapv #(update-in % [:key3] (fn [v] (mapv v [1 2]))) m)

For many data manipulations, the core functions like update-in and mapv do a good job on this kind of problem. There are several ways to use them subtly wrong and get back a sequence instead of a vector. The code can get a tad unwieldily as well sometimes.
The spectre library was created to make this kind of data manipulation easy and intuative:
hello.core> (require '[com.rpl.specter :refer :all])
nil
hello.core> (def data '[{:key1 val1 :key2 val2 :key3 [vector]}
{:key1 val1 :key2 val2 :key3 [vector]}])
#'hello.core/data
hello.core> (setval [ALL :key3] '[v2 v3] data)
[{:key1 val1, :key2 val2, :key3 [v2 v3]}
{:key1 val1, :key2 val2, :key3 [v2 v3]}]
It also tries to use the most efficient update method for each transformation.
This article gives a more complete (and opinionated) introduction.

(def m [{:key1 val1 :key2 val2 :key3 [:a :b :c :d]}
{:key1 val1 :key2 val2 :key3 [:a :b :c :d]}])
(mapv #(update % :key3 (fn [v] (mapv v [1 2]))) m)

Related

Clojure - Create an array of keys if value is true

I am totally new to clojure.
I have a JSON like: { "1": true, "2": false, "3": true, "4": false }
I want to create an array of keys for which the value is true in clojure. In this example the array should be ["1", "3"].
Please help me. Any help would be appreciated.
there are also couple of short and simple snippets for that:
user> (filter m (keys m))
;;=> ("1" "3")
user> (keep (fn [[k v]] (when v k)) m)
;;=> ("1" "3")
user> (for [[k v] m :when v] k)
;;=> ("1" "3")
If you're fine with using a vector instead of an array (since you're usually using vectors in Clojure anyway), you can do something like.
(defn keys-for-truthy-vals [m]
(->> m (filter val) (mapv key)))
Note The mapv is only so the map call returns a vector. If you want a seq, just use map.
The same as already provided, just staying in maps.
(keys (filter val m))
If your map is a Something like (->> (filter (fn [[k v]] v) a) (map (fn [[k v]] k))) will work. You can't do it with just a map because you need to drop certain values, so there will need to be some reducing or filtering.
There is built-in function in the Tupelo library for this:
(submap-by-vals map-arg keep-vals & opts)
Returns a new map containing entries with the specified vals. Throws for missing vals,
unless `:missing-ok` is specified. Usage:
(submap-by-vals {:a 1 :b 2 :A 1} #{1 } ) => {:a 1 :A 1}
(submap-by-vals {:a 1 :b 2 :A 1} #{1 9} :missing-ok ) => {:a 1 :A 1}
You could then just use the keys function on the resulting map.
Maybe this?
(->> foo (filter second) keys)
where foo is a map.

Checking String equals hash-map value Clojure - Cannot be cast

So i have a function that i am doing two if statements on, the first one appears to be working, but the second one is producing the error
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn user/sort-maps (NO_SOURCE_FILE:1559)
The function passes in two Map "m1 + m2" and two strings "s1 s2"
the problem piece of code is :
(if-not (= (get map1 s2)(s1))
What i am trying to do is see if string "s1" equals the string from value at "map s2" but i keep getting that error. This is the function i have
(defn sort-maps [map1 map2 s2 s1]
(if-not (contains? map1 s2)
[(assoc map1 s2 s1) map2]
[map1 (assoc map2 s2 s1)])
(if-not (= (get map1 s2)(s1))
[(dissoc map2 (get map1 s2))]
[map1 (assoc map2 s2 s1)]))
My input:
"door" "rood" "odor" "pen" "list" "silt" "#"
My output i want would be:
{"enp" "pen"}
As i only want to return words that cannot be made up into any other word in the input
munk rightly pointed out to the source of your error: (s1) tells Clojure to invoke s1 as a function with no arguments. However, even after fixing this, your code is unlikely to do what you expect it to do.
This is the body of the function sort-maps (with munk's correction):
(if-not (contains? map1 s2)
[(assoc map1 s2 s1) map2]
[map1 (assoc map2 s2 s1)])
(if-not (= (get map1 s2) s1)
[(dissoc map2 (get map1 s2))]
[map1 (assoc map2 s2 s1)])
The first if-not statement is executed and the result (either a pair of updated map1 and map2 or a pair of map1 and updated map2) is thrown away. Since maps in Clojure are immutable, assoc isn't going to add a new entry into the existing map - a new map is created instead. So, this function body is completely equivalent to just
(if-not (= (get map1 s2) s1)
[(dissoc map2 (get map1 s2))]
[map1 (assoc map2 s2 s1)])
I am not sure what you are doing here, so can't help you further.
s1 is a string, but by wrapping it in parens, you're making a function call. A string isn't a function, so that's why you're seeing the type error.
You probably want (if-not (= (get map1 s2) s1))
But you seem to have a deeper misunderstanding. Clojure data structures are immutable. This means the following:
user=> (def my-map {:a 1 :b 2})
#'user/my-map
user=> (assoc my-map :c 3)
{:a 1, :b 2, :c 3}
user=> my-map
{:a 1, :b 2}
user=> (dissoc my-map :a 1)
{:b 2}
user=> my-map
{:a 1, :b 2}
So you can't just change a value in place like you can with ruby or some other language.

apply function with multiple parameter with arguments from a vector

I have a function that takes three arguments say somefunction [param1 param2 param3] and a vector with values say [val1 val2 val3 val4 ...]
I would like to repeatedly call the somefunction with the first argument param1 as fixed and the other two parameters passed in the combination val1 val2, val2 val3, val3 val4 ... etc. which is equivalent to (somefunction sampleparam1 val1 val2) (somefunction sampleparam1 val2 val3) (somefunction sampleparam1 val3 val4)... etc. IS there a way to elegantly do this in clojure?
I'm not sure what you want to do with the results of each call to somefunction so I'll assume it is executed for its side-effects and use doseq.
(let [f (partial somefunction param1)]
(doseq [args (partition 2 1 [val1 val2 val3 val4])]
(apply f args)))
clojure.core/partition:
(partition 2 1 [1 2 3 4]) ;; ((1 2) (2 3) (3 4))
clojure.core/partial
(def plus10 (partial + 10))
(plus10 5) ;; 15
clojure.core/apply
(apply + [1 2 3]) ;; 6
Note: you can get away without using partial since apply accepts intervening arguments: (apply somefunction param1 args)
Vinoth, there is:
You are first asking to create a function that takes a maximum of three (3) arguments: [param1 param2 param3] but you want to be able to fix the first parameter. In addition, while you have stated that you have a vector of arguments it appears, from your write up, that you want to go through the vector of values such that the first call to somefunction takes the 1st and 2nd item from the vector, the second call takes the 2nd and 3rd item from the vector, and so on until the vector is exhausted.
The first part of your question (fixed first parameter) can be solved with:
(partial somefunction sampleparam1)
The second part is a bit more complicated and without more details I can only guess. But here is a small demonstration of one approach:
(defn somefunction
[param1 param2 param3]
(+ param1 param2 param3))
(def part (partial somefunction 100))
(let [x [1 2 3 4 5]
y (first x)
z (second x)]
(part y z))
If you could explain more about
Are the value vectors consistent in length?
What does somefunction do?
My first thought went to using reduce or loop but I hesitate to assume too much.
I think I get the gist of your question.
;; a function that returns the vector as pairs of sub-vectors
(defn pairs [v]
(map vector v (rest v)))
This splits your vector [val1 val2 val3...] into a sequence of pairs as you wanted.
Example output from this is
(pairs [1 2 3])
=> ([1 2] [2 3])
As #Kyle has pointed out you can also use (partition 2 1 v) here too, as you're only really interesting in sequences, not actual vectors as they get squashed later into parameters via apply.
Next, your function needs to do something. For illustration in the output, I'll just return a map with params as values to the keys :a :b :c
(defn somefn [p1 p2 p3]
{:a p1 :b p2 :c p3})
now create a new function that calls your function, but with a constant first arg. I'll just use :p1 as a marker. Thus you only have to call (partial-somefn p2 p3) which will call (somefn :p1 p2 p3)
(def partial-somefn (partial somefn :p1))
then to call it with pairs from your vector (or sequence)...
either use map...
(map #(apply partial-somefn %) (pairs [1 2 3]))
=> ({:b 1, :c 2, :a :p1} {:b 2, :c 3, :a :p1})
or use doseq for side effects...
(doseq [ps (pairs [1 2 3])]
(apply partial-somefn ps))
or a for loop
(for [ps (pairs [1 2 3])]
(apply partial-somefn ps))
=> ({:b 1, :c 2, :a :p1} {:b 2, :c 3, :a :p1})
You can see that the maps returned show the parameters were called in sequence with the constant first parameter.
So the condensed version is
(defn somefn [p1 p2 p3]
;; your code here
)
(def v [:v1 :v2 :v3])
(let [pairs (fn [v] (map vector v (rest v)))
pf (partial somefn :param1)]
(map #(apply pf %) (pairs v)))
or use one of the for or doseq variations depending on what somefn does

Best way to update several values in hashmap?

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

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