AOC 2021 day2 problem

Hello,

I try to solve the AOC day2 challenge ( Day 2 - Advent of Code 2021)
So far I have this:

(ns day2 
  (:require
   [clojure.string :as string]))

(def data ["forward 5"
           "down 5"
           "forward 8"
           "up 3"
           "down 8"
           "forward 2"])

(def position (atom {:horizontal 0
                       :depth 0}))

(defn parse-input 
  [[first second]]
   (cond 
     (= first "forward") (+ (position :horizontal ) second)
     (= first "up")      (- (position :depth ) second)
     (= first "down")    (+ (position :depth ) second)))

(defn part1 
  [data]
  (->> data
       (map #(string/split % #" "))
       (map #(parse-input  %) )))

(part1 data)

but I see this error in the repl

; Evaluating file: day2.clj
; 
; Error printing return value (ClassCastException) at day2/parse-input (day2.clj:18).
; class clojure.lang.Atom cannot be cast to class clojure.lang.IFn (clojure.lang.Atom and clojure.lang.IFn are in unnamed module of loader 'app')
; Evaluation of file day2.clj failed: class clojure.lang.ExceptionInfo
clj꞉day2꞉> 

what did I do wrong and how to solve this ?
and how can I return the position atom ??

You are trying to access the contents of the atom without de-referencing it, you need to do it with deref or just a @:

(defn parse-input 
  [[first second]]
   (cond 
     (= first "forward") (+ (@position :horizontal ) second)
     (= first "up")      (- (@position :depth ) second)
     (= first "down")    (+ (@position :depth ) second)))

But why are you using an atom? I’d question it because you probably don’t need mutable state here, pure functions will do it.

Cheers!

It also looks like the branches in your cond are trying to mutate the state of the atom, but they would not do that at all, they would simply return a series of numerical values. If you do want to solve this like you would in an imperative language, you need to use swap! inside your cond, combined with update to identify which keyword in your position atom you are trying to change. That could make this approach work, although you should then use doseq instead of map since you are traversing the data for the purpose of causing side effects, rather than trying to apply pure functions and gather the result, so map would not even do anything until you consume the results.

I agree with kolme, however, that you would be better off figuring out how to solve this without an atom and without mutation. As a hint, how could you use reduce combined with the position and the list of instructions, to yield an updated position?

I thought a atom would be the easist way to keep track of the horizontal and depth

and with the editing code I see this error :

; class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')

Yes, your input is strings, so split will give you strings, you need to parse the string containing a number if you want to do arithmetic with it.

Also note that, since you are in the context of a thread-last macro, this:

(map #(parse-input  %) )

can be simplified to just

(map parse-input)

Thanks,

Will take a big break now
I have this :


(def data ["forward 5"
           "down 5"
           "forward 8"
           "up 3"
           "down 8"
           "forward 2"])

(def position (atom {:horizontal 0
                       :depth 0}))

(defn parse-input 
  [[first second]]
   (cond 
     (= first "forward") (+ (@position :horizontal ) (read-string second))
     (= first "up")      (- (@position :depth ) (read-string second))
     (= first "down")    (+ (@position :depth ) (read-string second))))

(defn part1 
  [data]
  (->> data
       (map #(string/split % #" "))
       (map #(parse-input  %) ))
   (@position :depth))
  

(part1 data)

but that gives me 0 which is not right here

If it were me, I would probably move the string/split into parse-input, so it would begin like this, when including the parsing of the number:

(defn parse-input 
  [line]
    (let [[direction distance] (string/split line #"\s+")
          distance (parse-long distance)]
       …
)

You seem to have missed my response above that explains that you are not mutating the atom, which seems to be what you are trying to do, and which encourages you to study how you could use reduce to avoid needing an atom or mutation at all. (Even in that more idiomatic approach, you would still be using update to derive the new position information from the current position and direction, but you would simply be returning a new position to use as the result, rather than mutating anything.)

No, I did not miss that response
But I wanted to see if I can make this work but I cannot

Yes, my response explained why what you are trying to do cannot work.

Also, you should avoid read-string, it is overkill (and generally unsafe) compared to parse-long here.

I understand but I have to see what exactly to change to make it work

What your code does is look up a value from the position, and perform some arithmetic on it, and return the result. It does not change the atom in any way.

Thanks

After a break I will try it again.

Sounds good! To help point you more clearly in the directions I was trying to get at in my response: study how update can be used to get an entire new position value by applying a function to a key within the current position value. Then, to continue along the approach you started with, study how you can combine swap! with that update invocation, to actually change the atom (and then replace your use of map with doseq, so that your code actually runs: it won’t as things currently stand, because you never consume the value returned by your thread-last macro, and map is lazy, and should only be used with pure functions and when the value of map will actually be consumed).

Once you are ready to take a more Clojure-like approach, look at how you can eliminate the atom entirely and use that update invocation along with reduce (instead of doseq) to combine the position value with the series of move instructions to return the final location, without mutating anytying.

Good luck when you get back to this!

Thanks,

Sorry to say I do not see how swap and update should work here :

     (= first "forward") (swap! (@position :horizontal ) (+ (parse-long second) 5))))

Yes, you need to get to the point where you do understand that. For one thing, get rid of the parentheses around the + expression, and around (@position :horizontal), and get rid of the @. swap! takes an atom, a function, and its arguments. update is the function we want to use, and it is called with the old value of the atom, and takes a key that you want to compute a new value for, the function you want to call with that old value, and any additional arguments you want to give that function. So at a quick glance it would be closer to this:

(= first "forward")
(swap! position update :horizontal + (parse-long second))

(Also, that 5 absolutely doesn’t belong in there.)

Oops, sorry, didn’t notice until my edit that you were missing the update call. So update is the function we pass to swap!. It takes one version of the atom, and builds the other from it. I need to make a bit more of an edit to my explanation above to reflect that now.

All right, my explanation is fixed now. I would recommend working through some examples of how to effectively use swap! and update. They are fundamental to building mutations of data structures in Clojure. And then, when you are happy with this approach, it is also very much worth learning how to do without the atom entirely, and use update within reduce to step towards the answer without any mutable state.

Sorry, but when I print the value of horizontal with your edit it is still zero