DEV Community

Discussion on: AoC Day 4: Repose Record

Collapse
 
quoll profile image
Paula Gearon

The first part made me think a little about the data structure, so I decided to create a map of the guard ID to the guard's times, where the times is a map of the minute to the number of times that the guard was asleep in that minute.

Fortunately, the date/time format is year/month/day/hour/minute, which can be sorted alphanumerically, and once that's done all sleep and wake events occur in pairs together.

(def build-table
  [input-file]
  (loop [[line & xlines] (sort (lines input-file)) guard 0 table {}]
    (if-not line
      table
      (if (= "Guard" (subs line 19 24))
        (recur xlines (-> (subs line 26) (split #" ") first as-long) table)
        (let [sleep (as-long (subs line 15 17))
              wake (as-long (subs (first xlines) 15 17))
              table' (reduce (fn [t minute]
                               (update-in t [guard minute] #(if % (inc %) 1)))
                             table (range sleep wake))]
          (recur (rest xlines) guard table'))))))

Given the fixed-width, it seemed easier to substring out the bits I was looking for.

Now that I had this table, I just needed to search through it for the bits I was looking for. After trying to pull out a key/value entry from a map for the largest value, I decided to create a helper function that I could send to reduce which let me select the "largest" value, using an arbitrary function on the value in order to compare:

(defn max-finder
  [vfn]
  (fn [[_ mv :as mkv] [_ v :as kv]] (if (> (vfn v) (vfn mv)) kv mkv)))

Later on, someone pointed out that Clojure already has the function max-key which finds the key for the largest value, but if you want to look for a non-numeric value then you need to update the entries of the map. That meant that I ended up with a lot more code, and I don't think it was as elegant.

Anyway, now that I was reading the table and had my helper function, here is the first part:

(defn star
  [input-file]
  (let [table (build-table input-file)
        [guard times] (reduce (max-finder count) [0 []] table)
        [max-time _] (reduce (max-finder identity) [0 0] times)]
    (* guard max-time)))

Part 2 was similar. This time I created a temporary var to hold the function that (max-finder identity) returns, so that the map function doesn't keep calling it unnecessarily:

(defn star2
  [input-file]
  (let [table (build-table input-file)
        ident-finder (max-finder identity)
        guards-maxes (map (fn [[g tm]] [g (reduce ident-finder [0 0] tm)]) table)
        [guard [minute _]] (reduce (max-finder second) [0 [0 0]] guards-maxes)]
    (* guard minute)))

I like how having the correct data structure meant that the final calculations could be encapsulated in just a couple of lines.