Search code examples
clojureclojurescript

How do I find then update values in maps in a series of vectors in Clojure?


What would be an elegant way to find the ba4 values for :foo in a data structure like:

[[{:foo "ba1"}{:foo "ba2"}] [{:foo "ba3"}{:foo "ba4"}]]

and add the :bif "baf" key and value so the map(s) containing the key/value I sought so I had:

[[{:foo "ba1"}{:foo "ba2"}] [{:foo "ba3"}{:foo "ba4" :bif "baf"}]]?

This is the main thing I am trying to figure out; I do wonder about how you would do this if there were multiple levels of nested maps, but that will be next for me to understand.

I have, I think, seen various libraries out there for tackling this sort of thing and I'm not at all against using them, though they would have to work in ClojureScript.

I am trying to update a Reagent atom such that classes will change on a UI element.


Solution

  • (I learned a bit about decorators and merge from Rulle's solution.)

    What you're describing is walking a nested data structure. Postwalking or prewalking take every possible subform of your nested data structure and applies a function against it.

    Here's how I got your desired solution using postwalk (note: assoc adds a key-value to a map):

    (ns walking.core
      (:require [clojure.walk :as w]))
    
    (clojure.walk/postwalk #(if (and (map? %) (= (:foo %) "ba4")) (assoc % :bif "baf") %) 
      [[{:foo "ba1"} {:foo "ba2"}] [{:foo "ba3"} {:foo "ba4"}]])
    ;;=> [[{:foo "ba1"} {:foo "ba2"}] [{:foo "ba3"} {:foo "ba4", :bif "baf"}]]
    

    And it works no matter how nested it gets:

    (clojure.walk/postwalk #(if (and (map? %) (= (:foo %) "ba4")) (assoc % :bif "baf") %) 
      [[{:foo "ba1"} {:foo "ba2"}] [[{:foo "ba3"} {:test {:foo "ba4"}}]]])
    ;;=> [[{:foo "ba1"} {:foo "ba2"}] [[{:foo "ba3"} {:test {:foo "ba4", :bif "baf"}}]]]
    

    Why postwalk instead of prewalk? Both work.

    (clojure.walk/prewalk #(if (and (map? %) (= (:foo %) "ba4")) (assoc % :bif "baf") %) 
      [[{:foo "ba1"} {:foo "ba2"}] [{:foo "ba3"} {:foo "ba4"}]])
    ;;=> [[{:foo "ba1"} {:foo "ba2"}] [{:foo "ba3"} {:foo "ba4", :bif "baf"}]]
    

    You don't have to lein install anything, and it's part of Clojure; you just have to require it.