In clojure, to edit the values of the keys of a map, there are 2 options available:
update
assoc
The only difference that I could find from the clojure documentation is that the update function does the following extra thing:
If the key does not exist, nil is passed as the old value.
Are there any specific use cases where in I should use update or assoc other than this?
Or in which cases I should prefer what (If I have missed these use cases).
They both state clear intent in your code when used properly:
assoc hints to the reader, that new kv-pairs are added (or
replacing the values for existing keys) and that they tend to not be
relying on the current values for that keys.
update hints, that one value for a key gets updated and the original
value (if present) maybe used for this calculation.
(update {} :a (fnil inc 0))
; → {:a 1}
(update {:a 41} :a (fnil inc 0))
; → {:a 42}
(assoc {:z 0} :a 42 :b 23}
; → {:z 0, :a 42, :b 23}
You an emulate update with assoc as follows
(assoc m :foo (f (:foo m)))
which looks clunky and is better off as:
(update m :foo f)
which returns a new map with f applied on the the value corresponding to the :foo key.
Related
complete Clojure newbie here. This is a simple question, but I cant seem to get it:
Given that I have a nested hasmap of unknown depth, how do I use Specter's transform() to mutate the values of the data structure? I imagine a recursive-path is required here, but I cant get it to work. a working example is what I'm after and unfortunately, there isn't one in the docs.
(there is an example for set-val on a recursive map, but I don't know how to transform that into a transform use-case)
EDIT: More details were requested, so here they are:
I'm interested in a transform form that can mutate all the values of a nested maps - of any depth). for example, this transform would be able to increment all the values in the following maps (and any other nested map):
{:a 1 :b {:c 2 :d {:e 3}}}
AND
{:a 1 :b {:c 2}}
AND
{:a 1}
the line of code I'm interested in might look sth like this:
(transform <missing selector here> inc data)
Using the linked example:
(def MAP-NODES
(recursive-path [] p
(if-path map?
(continue-then-stay MAP-VALS p))))
(transform [MAP-NODES MAP-VALS number?] inc data)
Be sure to always study the Clojure CheatSheet. For something this simple, it might be easiest to just use clojure.walk/postwalk
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(dotest
(let [data {:a 1 :b {:c 2 :d {:e 3}}}
result (walk/postwalk (fn [item]
(if (int? item)
(inc item)
item))
data)]
(is= result {:a 2, :b {:c 3, :d {:e 4}}})))
For more advanced problems, you may be interested in the function walk-with-parents, which allows you to inspect all items on the path from the root node through to the current node.
I know how to create mutable vector:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))
=>my-namespace/my-vector
I know how to add element to that vector:
(dosync (alter my-vector conj "test"))
=>["test"]
Now I have "test" string in my mutable vector. But how I can remove it? I tried to use lazy-sequence function remove
(dosync (alter my-vector remove "test"))
but it doesn't work. How can I remove element from mutable vector?
EDIT:
The collection doesn't really have to be vector. I found solution for set, but will wait if someone will suggest solution for vector.
your mistake is that you pass the "test" arg to remove function, while it really accepts a predicate. In your case this one wold work (unproperly though, making a list instead of a vector):
(dosync (alter my-vector #(remove #{"test"} %)))
to keep it a vector you would have to do this:
(dosync (alter my-vector #(vec (remove #{"test"} %))))
what makes me curious about your example:
why would you use this weird construction:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))))
instead of just (def my-vector (ref []))
why do you use ref at all? would you be using it inside some transaction? if not, i would propose you to move to an atom (since it is also thread safe)
user> (def my-atom (atom []))
#'user/my-atom
user> (swap! my-atom conj "hello")
["hello"]
user> (swap! my-atom #(remove #{"hello"} %))
()
As specified in my edit, collection doesn't have to be really vector. #leetwinski posted answer how to do it for vector, but if I would decide to use set I found other solution. So if we create set the same way as in question replacing [] to #{}:
(defn create-vector []
(intern *ns* (symbol "my-set" (ref #{}))
=>my-namespace/my-vector
and add element:
(dosync (alter my-set conj "test"))
=>#{"test"}
, we can remove it
(dosync (alter my-set disj item))
=>#{}
I've just started learning Clojure, after many years of Java (and PHP/JavaScript) experience. What a challenge :-)
How do I update a map of values idiomatically? When I use the map function on a map it doesn't return a map, it returns a sequence.
I'm working on a small app where I have a list of tasks. What I'd like to do is alter some of the values in some of the individual tasks, then update the list of original tasks. Here are the tasks I'm testing with:
(defrecord Task [key name duration])
(def tasks
(atom
{
"t1" (->Task "t1" "Task 1" 10)
"t2" (->Task "t2" "Task 2" 20)
"t3" (->Task "t3" "Task 3" 30)
}
))
I've put the tasks in a hashmap, using a string key so it has fast, direct access to any task in the map. Each task holds the key as well, so I know what it's key is when I'm passing individual tasks to other functions.
To update the durations I'm using map and update-in to iterate over and selectively update the duration of each task, and returning the modified tasks.
Here's the function:
(defn update-task-durations
"Update the duration of each task and return the updated tasks"
[tasks]
; 1) Why do I have to convert the result of the map function,
; from a sequence then back to a map?
(into {}
(map
(fn [task]
(println task) ; debug
(update-in
task
; 2) Why do I have to use vector index '1' here
; to get the value of the map entry?
[1 :duration]
(fn [duration]
(if (< duration 20)
(+ duration 1)
(+ duration 2)
)
)
)
) tasks))
)
I print the before/after values with this:
(println "ORIGINAL tasks:")
(println #tasks)
(swap! tasks update-task-durations)
(println "\nUPDATED tasks:")
(println #tasks)
1) The main problem I'm having is that the map function returns a sequence, and not a map, so I'm having to convert the sequence back to a map again using into {} which seems to me to be unnecessary and inefficient.
Is there a better way to do this? Should I be using a function other than map?
Could I arrange my data structures better, while still being efficient for direct access to individual tasks?
Is it ok to convert a (potentially very large) sequence to a map using into {} ?
2) Also, inside my function parameter, that I pass to the map function, each task is given to me, by map, as a vector of the form [key value] when I would expect a map entry, so to get the value from the map entry I have to pass the following keys to my update-in [1 :duration] This seems a bit ugly, is there a better/clearer way to access the map entry rather than using index 1 of the vector?
A popular way to solve this mapping-over-maps problem is with zipmap:
(defn map-vals
"Returns the map with f applied to each item."
[f m]
(zipmap (keys m)
(map f (vals m))))
(defn update-task-durations
[tasks]
(let [update-duration (fn [duration]
(if (< duration 20)
(+ 1 duration)
(+ 2 duration)))]
(->> tasks
(map-vals #(update % :duration update-duration)))))
(swap! tasks update-task-durations)
For Clojure < 1.7, use (update-in % [:duration] ... instead.
Alternatively, you could also use destructuring to simplify your current solution without defining a utility function:
(->> tasks
(map (fn [[k task]]
[k (update task :duration update-duration)]))
(into {})
Why?
map only deals with sequences. If you're into type signatures, this means that map always has the same type (map :: (a -> b) -> [a] -> [b]), but it also means that all you'll get out of map is a seq-of-something.
map calls seq on its collection parameter before doing anything, and seq-ing a map gives you a sequence of key-val pairs.
Don't worry too much about efficiency here. into is fast and this is pretty idiomatic.
Just get more alternatives:
Instead of a map you can use a for
(into {}
(for [[key value] your-map]
[key (do-stuff value)]))
A faster way is reduce-kv
(reduce-kv
(fn [new-map key value]
(assoc new-map key (do-stuff value)))
{}
your-map))
Of course you can also use a simple reduce
(reduce (fn [m key]
(update m key do-stuff))
your-map
(keys your-map))
From core.clj, the definition of assoc (trimmed of metadata etc)
(def assoc
(fn assoc
([map key val] (. clojure.lang.RT (assoc map key val)))
([map key val & kvs]
(let [ret (assoc map key val)]
(if kvs
(if (next kvs)
(recur ret (first kvs) (second kvs) (nnext kvs))
(throw (IllegalArgumentException. "assoc expects even no of args...")))
ret)))))
what does the let binding of ret achieve? Why is it not just:
(def assoc
(fn assoc
([map key val] (. clojure.lang.RT (assoc map key val)))
([map key val & kvs]
(if kvs
(if (next kvs)
(recur (first kvs) (second kvs) (nnext kvs))
(throw (IllegalArgumentException. "assoc expects even no of args...")))
ret))))
Rudimentary testing (in ClojureCLR) seemed to indicate the latter works ok, and I can't find any documentation indicating the use of the former. Any clues?
Thanks, gary
Your version should fail to compile because not enough arguments are passed to recur (four are needed in this case). The let exists to bind the result of association of the first pair of key/values passed to the first body (3-arity) as ret. In case the second body was invoked by itself earlier using recur with kvs = nil (as a result of calling nnext in the recur form as the fourth argument) (if kvs ..) will fail and ret can simply be returned. If there are more kvs tailrecursion can happen.
I have a Clojure function with parameter: [{:keys [from to]}]. Both the from and to keys are optional. I would like to remap these keys into new keys but maintaining the values. What's an efficient/idiomatic way to go about this? Here's my curent solution.
(defn query
[{:keys [from to]}]
(let [re-map {:$gte from
:$lt to}]
(into {} (remove #(nil? (val %)) re-map))))
I need it to return either one or both, and no nil if one of the key wasn't entered. So
=>(query {:from 10})
{:$gte 10}
and
=>(query {:from 10 :to 20})
{:$gte 10 :lt 20}
There is a function for this in the clojure.set namespace called rename-keys:
user=> (use 'clojure.set)
nil
user=> (rename-keys {:from 10} {:from :$gte :to :$lt})
{:$gte 10}
You can use destructuring for this:
user=> (defn re-map [{the-x :x the-y :y}] the-x)
user=> (re-map {:x 10 :y 1})
10
I would recommend this excellent introduction with lot of examples.