This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-30
Channels
- # announcements (1)
- # babashka (15)
- # calva (3)
- # cider (1)
- # clj-kondo (16)
- # clj-on-windows (1)
- # cljfx (1)
- # clojure (25)
- # clojure-europe (6)
- # clojure-spec (15)
- # cursive (13)
- # emacs (11)
- # fulcro (2)
- # humbleui (7)
- # introduce-yourself (1)
- # jackdaw (1)
- # off-topic (10)
- # pathom (5)
- # portal (3)
- # re-frame (7)
- # reagent (12)
- # releases (1)
- # shadow-cljs (8)
- # tools-build (18)
- # web-security (10)
Hello! What is the idiomatic clojure way of updating a map inside a vector (that may also live in another map)? Example:
{:cast "muppets"
:characters
[{:name "kermit"
:address "foo"}
{:name "gonzo"
:address "faa"}
{:name "pepe"
:address "fii"}]}
I want to update the address for the vector item that has a :name
that equals "gonzo"
to sesame street
and have the whole data structure updated like:
{:cast "muppets"
:characters
[{:name "kermit"
:address "foo"}
{:name "gonzo"
:address "sesame street"}
{:name "pepe"
:address "fii"}]}
You'll want to us a combination of update
, assoc
, and map
(let [m {:cast "muppets"
:characters
[{:name "kermit"
:address "foo"}
{:name "gonzo"
:address "faa"}
{:name "pepe"
:address "fii"}]}
m (update m :characters (fn [characters]
(map (fn [character]
(if (= (:name character) "gonzo")
(assoc character :address "sesame street")
character)))))]
;; m now has the updated nested map
)
the alternative is to no keep characters as
[{:name "kermit"
:address "foo"}
{:name "gonzo"
:address "faa"}
{:name "pepe"
:address "fii"}]
but
{id {:name "kermit"
:address "foo"}
id2 {:name "gonzo"
:address "faa"}
}
right, if your characters were a map keyed by name or id or whatever, it changes it quite a bit
(let [m {:cast "muppets"
:characters
{:kermit {:name "kermit"
:address "foo"}
:gonzo {:name "gonzo"
:address "faa"}
:pepe {:name "pepe"
:address "fii"}}}
m (update-in m [:characters :gonzo :address] "sesame street")]
;; m now has the updated nested map
)
Much simpler, for sure
I'm using something in the lines of:
(defn- index-of [pred coll]
(first (keep-indexed #(when (pred %2) %1) coll)))
.....
(let [idx (index-of #(= (:name %) muppet) (get-in m [:characters]))]
(assoc-in db [:characters idx :address] address))))
in my personal taste simplicity is always on the first place. Unless it has to be be really good performance, then it can be a little more complex, but not too much 😉
others may have a different view, but I tend to arrange my data structures as maps if I need keyed access/update into them. You're traversing the data structure several times, first to get the index, and then to do the assoc-in
. Obviously depending on the size of the structure that may be cumbersome
though of course if your data is coming from some source in that format, you'll have to traverse it to make it accessible, so perhaps it's not as problematic?
(defn group-by-key
[k ms]
(reduce (fn [acc m] (assoc acc (get m k) m))
{}
ms))
(group-by-key :name [{:name :foo}
{:name :bar}])
;; => {:foo {:name :foo}, :bar {:name :bar}}
(def local-data (atom (-> data-from-source
(update :characters (partial group-by-key :name)))))
(def update-character
[name address]
(swap! local-data
(fn [data]
(update-in data
[:characters name :address]
address)))
Code below not tested: If you want to use the index, you can at least take advantage to short circuit on a match.
(defn index-of [pred coll]
(some (fn [i] (and (pred (coll i)) i)) (range (count coll))))
If you are okay with not short circuiting, I think this can be much simpler by mapping a conditional transformation over the whole vector.
(update m :characters
#(map (fn [c]
(if (pred c) (transform c) c))
%))
If you don’t mind bringing in Specter as a dependency, you could do something like this: (Specter/transform [:characters ALL (pred #(= (:name %) "gonzo"))] #(assoc % :address "Sesame Street") <data>)
. This is totally untested, so might even be wrong, but Specter is built to handle this sort of nested transforms and selects.
Anyone remember the name of the library that converts jetty errors into something much nicer? Can’t remember nor work out how to google for it!
Did something change for org.clojure/tools.logging {:mvn/version "1.2.4"}
and -Dclojure.tools.logging.factory=clojure.tools.logging.impl/jul-factory
? I use Java 11.
Whenever I log something I always see clojure.tools.logging$eval3401$fn__3404 invoke
which wasn’t there some time ago. It started to appear in all projects on my computer. Whenever it is builded to jar or REPL.
The same code still work as expected in google cloud, but not on my computer.
I miss something obvious or I have some kind of dementia ;)
(l/info "foo")
Oct 30, 2022 10:19:24 PM clojure.tools.logging$eval3401$fn__3404 invoke
INFO: foo