Andrey Listopadov

Advent of Code: Day 1

@aoc2021 @programming clojure ~4 minutes read

Welcome to the series of posts on this year’s Advent of Code event! This year, to keep myself motivated, I decided to write a small post each day, in which I’ll describe the way I’ve approached this day’s AOC puzzle. As I’ve mentioned I’ll be solving puzzles in Clojure, (usually) formatting the code as can be seen in the REPL. So if you see a code block that starts with day1>, it means that we’re inside the REPL, and in a day1 namespace. And, without further ado, let’s begin!

Sonar Sweep

The first task is about calculating the rate at which the depth is increasing. Should be pretty simple!

For the given example input:

199
200
208
210
200
207
240
269
260
263

We need to count the number of times a depth measurement increases from the previous measurement. This means, that we need to check if 200 is greater than 199, then that 208 is greater than 200, and so on. Sounds like a job for reduce! Let’s write such algorithm, and only partially supply the input:

user> (ns day1)
nil
day1> (reduce (fn [res [current next]]
                (+ res (if (> next current) 1 0)))
              0
              [[199 200] [200 208]])
2

This correctly returns 2, now let’s feed it the whole input! But we need to regroup the input first, so there will be a group of two for each number. Luckily we have a function just for that, called partition:

day1> (partition 2 1 [199 200 208 210 200 207 240 269 260 263])
((199 200)
 (200 208)
 (208 210)
 (210 200)
 (200 207)
 (207 240)
 (240 269)
 (269 260)
 (260 263))

Sweet! Let’s put our reduce into a function that does this transformation, and plug this input into it!

day1> (defn part-1 [input]
        (reduce (fn [res [current next]]
                  (+ res (if (> next current) 1 0)))
                0
                (partition 2 1 input)))
#'day1/part-1
day1> (part-1 [199 200 208 210 200 207 240 269 260 263])
7

And it returns the correct result for the test input! Now we can try with a real one.

I’ll not be sharing the whole input in the blog, but you can always see it in my GitHub repo for AoC.

First, we need a function, that will read and parse the input file:

day1> (require '[clojure.string :as str])
nil
day1> (defn- read-input []
        (->> "inputs/day1"
             slurp
             str/split-lines
             (map #(Integer/parseInt %))))

I’m putting all input files into the inputs directory, named after the respecting day of the event. In this case, it’s inputs/day1. We slurp this file, and split-lines so there is a sequence of strings, each representing a line of the file. Then we map over it with an anonymous function, that uses Integer/parseInt a Java method that parses the string as an integer. This may throw a NumberFormatException of course, but I trust that the author of the event provides a valid input.

Now, let’s plug this input into our part-1 function:

day1> (part-1 (read-input))
1215

And we surely get the right answer!

I’ve also realized that instead of reduce we can use filter and just count the amount of remaining numbers, like this:

day1> (defn part-1 [input]
        (->> input
             (partition 2 1)
             (filter (partial apply <))
             count))
#'day1/part-1
day1> (part-1 (read-input))
1215

It yields the same result but is much more concise. I’ll keep this solution.

Time for part two.

Part two

Part two changes how the calculations are done a bit. Now we need to do a sliding sum of groups of three. This means, that for the test input, we need to check if the sum of the first three numbers, in our case these are 199, 200, 208, is smaller than the sum of three numbers, starting from the second number: 200, 208, 210. This grouping is again can be achieved by partitioning:

day1> (partition 3 1 [199 200 208 210 200 207 240 269 260 263])
((199 200 208)
 (200 208 210)
 (208 210 200)
 (210 200 207)
 (200 207 240)
 (207 240 269)
 (240 269 260)
 (269 260 263))

Now we just need to sum these subsequences with a map:

day1> (->> [199 200 208 210 200 207 240 269 260 263]
           (partition 3 1)
           (map (partial apply +)))
(607 618 618 617 647 716 769 792)

And we can apply our part-1 function on the new input:

day1> (->> [199 200 208 210 200 207 240 269 260 263]
           (partition 3 1)
           (map (partial apply +))
           (part-1))
5

Again, we get a correct answer for the test input, so let’s put this into a function, and pass it a real one:

day1> (defn part-2 [input]
        (->> input
             (partition 3 1)
             (map (partial apply +))
             part-1))
#'day1/part-2
day1> (part-2 (read-input))
1150

And we get a correct answer!

Day 1 thoughts

This wasn’t hard, but no need to relax yet. More complex tasks are surely on the way!

I hope you’ve liked this short post of me describing the thought process for solving this day’s task. I’ll (hopefully) be publishing such posts each day, up to the event’s end. We’ll see how far I’ll manage to get!

See you in the next post!