braveandtrue 2018-09-14

Hi @manutter51, can you give me some pointers with implementing update-in and assoc-in?

I’ve been messing around… and came up with

not much really hehe

There’s probably a nice recursive function for that

(def character
  {:name "Smooches McCutes"
   :attributes {:intelligence 10
                :strength 4
                :dexterity 5}})

(assoc-in character
  [:attributes :intelligence] 19)
; --> {:name "Smooches McCutes",
 :attributes {:intelligence 19, :strength 4, :dexterity 5}}

(update-in character
  [:attributes :intelligence] + 19)
; --> {:name "Smooches McCutes",
 :attributes {:intelligence 29, :strength 4, :dexterity 5}}

; and 
(assoc (:attributes character) :intelligence 26)
; --> {:intelligence 26, :strength 4, :dexterity 5}

; stuck here...
;(assoc (assoc :intelligence 26)

I’m trying to write out what happens in assoc, manually using assoc

before trying to write the function

it feels like reduce might work

but first, I want to write it manually for (assoc-in character [:attributes :intelligence] 19) with just assoc

can you help me with that part?

character is a map, and inside that map is another map, under the key :attributes

and inside the inner map is a key called :intelligence that you want to change.

(booting up a sandbox repl, just a sec)

I’m here …

(assoc character :attributes (assoc (:attributes character) :intelligence 26))
; --> {:name "Smooches McCutes",
 :attributes {:intelligence 26, :strength 4, :dexterity 5}}

I think this would be more clear with 3 levels one sec…

(def nested-map {:name "bob" 
    :attributes 
        {:strengh 10
         :dexterity 5
         :intelligence {:spatial 50
                        :verbal 20
                        :math 53}}})

lol, where have I seen that before 😄

(assoc nested-map :attributes (assoc (:attributes nested-map) :dexterity 100))
; --> {:name "bob",
 :attributes {:intelligence {:spatial 50, :verbal 20, :math 53},
              :dexterity 100,
              :strengh 10}}


(assoc nested-map :attributes (assoc (:attributes nested-map) :intelligence (assoc (:intelligence (:attributes nested-map) ) :spatial 50 ) ) ; doesn't work,... I'm lost in here

(assoc nested-map :attributes (assoc (:attributes nested-map) :intelligence {:a 10 :b 20}))

(assoc nested-map :attributes (assoc (:attributes nested-map) :intelligence (assoc (:intelligence (:attributes nested-map))  :spatial 10 )))

(assoc nested-map :attributes 
        (assoc (:attributes nested-map) :intelligence 
               (assoc (:intelligence (:attributes nested-map)  
                        :spatial 10))))

Let’s throw in one more:

(def simple
  {:name "Smooches McCutes"
   :intelligence 10})

(assoc simple :intelligence 10)

The compare:

(assoc simple :intelligence 10)

(assoc character :attributes
                 (assoc (:attributes character)
                   :intelligence 26))

(assoc nested-map :attributes
                  (assoc (:attributes nested-map)
                    :intelligence (assoc (:intelligence (:attributes nested-map))
                                    :spatial 60)))

yes those are three good representative cases. There is probably something about nil and identity too

Probably, but we can ignore those for now

Now, could you break that last one down into 3 functions you could call to assoc a new value into the inner :spatial key?

I’m thinking a function to set the :attributes key, a function to set the :intelligence key, and one to set the :spatial key.

the 3rd nested case?

“last one” == (assoc nested-map...)

so like (f :attributes (g :intelligence (h :spatial 10)))

Kinda, except each function is going to need to take the map it’s working with

No global variables allowed 😉

is there a len function?

Since I can’t do this:

(defn my-assoc-in
  ([m k v] (assoc m k v))
  ([m [k & ks] v] (println "This is the same arity... bummer.")))

or is ks nil when there is no seq

I’ll check that out

(defn printks [m [k & ks] v] (println ks))
(printks {:a 1 :b 2} :happy "yes")
; --> UnsupportedOperationException nth not supported on this type: Keyword  clojure.lang.RT.nthFrom (RT.java:947)

What ever that means

For arrays just use count

“For vectors, just use count

bleh is it friday yet?

(defn printks [m [k & ks] v] (println (count ks)))
doesn’t work…

I was thinking if count(ks) == 0 then just assoc

otherwise recurse

What arguments are you calling it with?

(printks {:a 1 :b: 2} :happy "yes")

Ok, your :happy needs to be [:happy]

You want to match the signature for assoc-in, which takes (assoc-in coll ks v ...), where ks is a vector of keys.

I see that’s why assoc-in needs those keys in a vector

if I destructure I can’t count can I?

if I had [col ks v], I could count ks

but with [[k & ks]] ks might not be a vector

(defn my-assoc-in
  [m [k & ks] v] 
  (if (empty? ks)
    (assoc m k v)
    ()));recurse stuff here

ks will always be a vector, but it might be empty

What you’ve got so far looks promising

If you’re having trouble with the “recurse” part, try writing functions assoc-attributes, assoc-intelligence, and assoc-spatial, so that you can combine them to update the :spatial value.

defn my-assoc-in
  [m [k & ks] v] 
  (if (empty? ks)
    (assoc m k v)
    (assoc m k (my-assoc-in (get m k {}) ks v))))

except,… I didn’t get it by thinking.

You’d be surprised how much thinking is futzing. 😉

That’s what makes the repl so useful.

Yeah, I wish I could “see” it though. Maybe I just need a bunch more practice

Thanks for thinking it through with me!

Seems like update-in will be like

(defn my-update-in
  [m [k & ks] func & args]
  (if (empty? ks)
    (update m k func args)
    (update m k (my-update-in (get m k {}) ks #(func args ???)))))
;; ?? should be something that would get the value

mostly, except args will be a vector because of the &, so you’ll need an apply to spread the args back out into a normal function call

(apply func args) that is.

and whatever ??? it

since it seems to pass the value of that last nested key to the function

oh wait, no I’m leading you astray, my bad

You want to just pass that function thru until you get down to your base case

apply will happen on the base case

Right, that’s where you need it

otherwise it’s just func

There’s actually something interesting happening when you add & args to a recursive function.

(defn my-update-in
  [m [k & ks] func & args] 
  (if (empty? ks)
    (update m k (apply func args (get m k))) 
    (update m k (my-update-in (get m k {}) ks func))))

Not quite. You need to pass a function to update

Although it would be simpler if you just assoc in your base case

(assoc m k (apply func args (get m k)))

I think that would work.

Your non-base case isn’t passing the args thru, you’ll need that.

But then you’ll run into something really wacky: every time you recurse, the args get wrapped inside a vector, so if you call (my-update-in m [:a :b :c] f 1 2 3), it’s going to get to the base case as (my-update-in m [:c} [[[1 2 3]]])