Fork me on GitHub
#braveandtrue
<
2018-09-14
>
moo18:09:10

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

moo18:09:27

I’ve been messing around… and came up with

moo18:09:48

not much really hehe

manutter5118:09:16

There’s probably a nice recursive function for that

moo18:09:57

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

moo18:09:11

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

moo18:09:18

before trying to write the function

moo18:09:44

it feels like reduce might work

moo18:09:08

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

moo18:09:13

can you help me with that part?

manutter5118:09:58

so let’s see…

manutter5118:09:56

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

manutter5118:09:23

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

manutter5118:09:42

(booting up a sandbox repl, just a sec)

moo18:09:09

I’m here …

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

moo18:09:46

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

moo18:09:02

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

manutter5118:09:19

lol, where have I seen that before 😄

moo18:09:20

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


moo18:09:45

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

moo18:09:57

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

moo18:09:11

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

moo18:09:12

that works

manutter5118:09:20

Yay, there you go

moo18:09:02

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

moo18:09:04

more readable

manutter5118:09:09

Let’s throw in one more:

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

(assoc simple :intelligence 10)

manutter5118:09:24

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

moo18:09:09

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

manutter5118:09:25

Probably, but we can ignore those for now

manutter5118:09:05

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

moo18:09:37

which last one?

manutter5119:09:03

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

moo19:09:07

the 3rd nested case?

moo19:09:18

ahh okay

manutter5119:09:19

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

moo19:09:48

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

manutter5119:09:54

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

manutter5119:09:03

No global variables allowed 😉

moo19:09:50

is there a len function?

moo19:09:34

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

moo19:09:57

or is ks nil when there is no seq

moo19:09:01

I’ll check that out

moo19:09:55

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

moo19:09:59

What ever that means

manutter5119:09:18

For arrays just use count

manutter5119:09:23

vector I mean

manutter5119:09:33

“For vectors, just use count

manutter5119:09:43

bleh is it friday yet?

moo19:09:27

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

moo19:09:41

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

moo19:09:50

otherwise recurse

manutter5119:09:56

What arguments are you calling it with?

moo19:09:04

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

manutter5119:09:35

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

moo19:09:51

b/c [[]]

manutter5119:09:40

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

moo19:09:51

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

moo19:09:55

if I destructure I can’t count can I?

moo19:09:10

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

moo19:09:27

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

moo19:09:30

is that right?

moo19:09:57

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

manutter5119:09:02

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

manutter5119:09:54

What you’ve got so far looks promising

manutter5119:09:20

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.

moo19:09:41

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

moo19:09:53

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

moo19:09:08

just futzing

manutter5119:09:09

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

manutter5119:09:21

That’s what makes the repl so useful.

moo19:09:02

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

moo19:09:13

Thanks for thinking it through with me!

moo19:09:36

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

manutter5119:09:51

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

manutter5119:09:07

(apply func args) that is.

moo19:09:17

and whatever ??? it

moo19:09:37

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

manutter5119:09:04

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

manutter5119:09:35

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

moo19:09:01

apply will happen on the base case

manutter5119:09:11

Right, that’s where you need it

moo19:09:15

otherwise it’s just func

manutter5119:09:01

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

moo19:09:29

something like:

moo19:09:32

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

manutter5119:09:18

Not quite. You need to pass a function to update

manutter5119:09:34

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

manutter5119:09:58

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

manutter5119:09:06

I think that would work.

manutter5119:09:57

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

manutter5119:09:18

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