Skip to content

theianjones/aoc_2021

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Today I learned

Day 1

Reading in files

I learned how to use the resources folder to place my input.txt files in. First I had to add the resources folder to the class path.

You do this in deps.edn

{:paths ["src" "resources"]}

Now Java knows where the resources file is.

Next we use clojure.java.io to read in the file.

;; storing day1 input in day1.txt
(->> io/resource "day1.txt"
     io/reader)

This reader will take the file and create a java.io.BufferedReader object. We can pass this to line-seq to turn it into a sequence of strings.

(->> io/resource "day1.txt"
     io/reader
     line-seq
     (mapv #(Long/parseLong %)))

Now we can map over it and turn the strings to numbers, returning a vector.

General Approach

I figured that I needed to map the values into their respective windows and then count how many windows increased. I did it like this originally:

(defn count-increased [col]
  (reduce-kv (fn [count index value]
               (if (< value (nth col (inc index) -1))
                 (inc count)
                 count))
             initial-count col))

(count-increased (into [] (remove nil? (map-indexed (fn [index n]
                                                      (let [n2 (nth input (inc index) nil)
                                                            n3 (nth input (inc (inc index)) nil)]
                                                        (when (and (some? n2)
                                                                   (some? n3))
                                                          (+ n n2 n3)))) input))))

This the problem is that we are going over the whole input in 2 passes when it can be done in one. So I combined it to one reduce:

(defn optimized [col]
  (reduce-kv (fn [count index value]
               (let [value2 (nth col (inc index) nil)
                     value3 (nth col (inc (inc index)) nil)
                     value4 (nth col (inc (inc (inc index))) nil)
                     window1 (add-not-nil value value2 value3)
                     window2 (add-not-nil value2 value3 value4)]
                 (if (and  (not (nil? window1)) (not (nil? window2)))
                   (if (< window1 window2)
                     (inc count)
                     count)
                   count))) 0 col))

(optimized input)

Theres a whole bunch of let bindings to find the next values in the collection, then I have to build the two windows for part 2. I have to account for nil values because you cant pass nil to +. I was relatively happy with this and then I looked at some of the clojurian slack answers.

Use apply and partition

The biggest problem with my solution is I’m doing a bunch of things manually like getting the next values in the sequence and then comparing them individually.

Using apply and partition you can reduce the lines of code for count-increased drastically:

(defn new-count-increased [col] (count (filter #(apply < %) (partition 2 1 col))))

We can use partition to create the data structure we really want… so from [1 2 3 4] to ((1 2) (2 3) (3 4)). Notice this doesnt create any nil values to protect against. The second number passed to partition is the padding, basically how many numbers the next collection starts from the first collection (no pad means there is no overlap).

Then we filter for all the pairs where the first depth is less than the second. We can use apply to drop down into the collection and apply that method to the params that filter passes us.

Then we just count how many times that happened!

For the second part, you can use partition again to create the windows and then sum those partitions up and pass that collection into our now function.

(new-count-increased (map #(apply + %) (partition 3 1 input)))

apply is doing the heavy lifting of adding the partitions up.

Day 2

learning more core functions

I created a solution here that uses my own set-position function. It basically takes a map, key, and value and updates that map key with whatever value I passed it.

(defn set-position [state key value]
  (assoc state key (+ value (get state key))))

(set-position {:depth 0 :distance 0} :depth 1)
;; => {:depth 1 :distance 0}

Then I looked at Borkdude’s solution and he is using update for this purpose! It does almost exactly what I intended. It takes an “associative structure”, a key and a function to apply to that key to apply the function to. The function talks the current value in the structure and any other args you passed.

(update {:depth 0 :distance 0} :depth + 1)
;; => {:depth 1 :distance 0}

This allowed me to delete a function and use clojure.core!

-> and ->> are more readable

Im not used to using the thread macros but they make things a lot easier to read. The mental model is “heres the input and then the sequential functions to apply to that input”. Its like | piping in bash.

Day 3

Today was all about breaking the larger problem into smaller pieces. It was a recursion problem, so I had to learn about loop / recur.

loop / recur

Todays solution required looping over the input collection n amount of times when n is the length of the binary number passed in. So 00101 would require 5 loops.

For each n column in the bitwise numbers I had to do a couple things:

  • find the most common bit in that column 1 or 0
  • filter the input by the most common bit found
(defn filter-ratings [col n pred]
  (let [threshold (/ (count col) 2)
        positive-bit-count (nth (apply map + col) n)
        rating-filter (if (pred positive-bit-count threshold) 1 0)]
    (filter #(= (nth % n) rating-filter) col)))

I found the most common bit by:

  • finding the total bitwise number count
  • counting how many 1 there are in that column
  • then applying (if (pred positive-bit-count threshold) 1 0) to find which bit I need to filter by

This is what I have to do for each column in the bitwise number. So given our previous number 00101 I would have to do this 5 times.

loop lets you define which variables are tracked across the recursion. Here I need to track which bit column Im on and the result of running get-ratings.

You can see the base case for this is whether n has increased to the count of the col (this would mean somethings broken) or if there is 1 item in result.

If there is I need to flatten it because it will be a seq in a seq ((000101)). Then I need to turn it back to a string.

(defn find-rating [pred col]
  (loop [n 1
         result (filter-ratings (map to-int-seq col) 0 pred)]
    (if (or (= (count col) n) (= (count result) 1))
      (apply str (flatten result))
      (recur (inc n) (filter-ratings result n pred)))))

Now we can create our 2 different ratings and find the answer:

(def oxygen-rating (partial find-rating >=))
(def c02-rating (partial find-rating <))

(->> input
     ((juxt oxygen-rating c02-rating))
     (map parse-binary)
     (apply *))

Day 6

Original solution:

(defn day [fishies]
 (reduce-kv (fn [new-fishies index fish]
              (if (zero? fish)
                (conj (update new-fishies index (fn [_] 6)) 8)
                (update new-fishies index dec))) fishies fishies))

This worked for the first part because 80 days isnt a huge number. But adding a number to the array every time for 256 iterations becomes out of control.

Today was learning about how to use efficient data structures. There are only 9 types of unique fish, so you dont have to store them individually. Their day number is all that really matters.

This is where frequencies can help. Given the example input:

(def example [3 4 3 1 2])

(frequencies example)
;; => {3 2, 4 1, 1 1, 2 1}

We can see how frequencies will count up the type of fish we want.

The next important idea is that we need to iterate for n days. Clojure gives us this function where it will call the same function with passing the returned value to the next call.

A simple example:

(nth (iterate inc 0) 10)
;; => 10

It will keep applying f to x so in this example we get (inc (inc (inc (inc (inc (inc (inc (inc (inc (inc 0)))))))))). You can see why this is useful.

Given our fishies example above, we can create a step function to pass to our iterate call to multiply the fishies per the rules

(defn day [fishies]
  (reduce (fn [new-fishies [fish-days total-fish]]
               (if (zero? fish-days)
                 (-> new-fishies
                     (update 6 (fnil + 0) total-fish)
                     (update 8 (fnil + 0) total-fish)
                     (update 0 (fnil - 0) total-fish))
                 (-> new-fishies
                     (update fish-days (fnil - 0) total-fish)
                     (update (dec fish-days) (fnil + 0) total-fish)))) fishies fishies))

(nth (iterate day (frequencies example)) 80)
;; => {0 424, 7 370, 1 729, 4 739, 6 991, 3 790, 2 558, 5 762, 8 571}

After 80 days, you can see that we have a lot of fish! Now we can get the values, and add them up to get the total:

(-> (iterate day (frequencies example))
    (nth 80)
    (vals)
    (->> (apply +)))
;; => 5934

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published