Intro

I’ve been looking for an excuse to write some Clojure and the Advent of Code is always a fun set of katas. Who knows maybe this year I’ll even finish them on time! :-) If you don’t know the Advent of Code is a series of code challenges that runs through well Advent. There is a new challenge every day and they come in two parts.

Day 1 - Part 1

Okay! Exciting! Day one! Let’s see what is in store for us today! Several elves have backpacks containing snacks and these are represented by a list of numbers separated by new lines. Each number represents the calories in a snack and a new line marks the end of one backpack.

The goal of part one is to find the calorie total of the backpack with the most calories in it. Neat!

Input

AoC gives fairly long inputs so I normally just dump them into a .txt file and call slurp to read that file. For this problem I’ve got a list of numbers separated by newlines like I mentioned above.

2121
12876
11330

4029
11446
... many many lines below

First things first: Parsing the input

Thankfully slurp is really handy for these little exercises. It’ll read a file for you and return the contents as a string. Because I’m just going to be hacking in a REPL I’m going to use def to declare it as a constant. In the real world I’d probably use a command line argument but this works fine. If you don’t know about slurp it check out the docs! Fun fact, slurp can also be given a URL to read a web-page! :-)

(ns advent-of-code.day-one
  (:require [clojure.string :as str]))

(def input (-> (slurp "./advent-of-code/day1/input.txt")
	     str/split-lines))

Which gives us a nice starting point going forwards.

(take 10 input)
("9524" "12618" "6755" "2121" "12876" "11330" "" "4029" "11446" "11571")

Right so… whats the plan now I have the input ready to go. Well…I need to calculate the calorie total for each backpack so calculating it for one backpack seems like a good start. I want to take from input until I hit a newline / the end of the current backpack. I know take-while is a function that takes a predicate and collection and will take an element from the col while the predicate is true.

(take-while even? [ 2 4 8 9 10 ])
(2 4 8)

So if I do that but use clojure.string/blank? as the predicate then grabbing the contents of the first backpack should work. I could use drop-while and then rest to drop the empty string and move the second backpack to the start of the collection. Having to go through the list twice seems annoying though. Let me have a Google…

Okay cool! There is a function for that! Of course there is! haha! I can use split-with which does actually use both take-while and drop-while and returns a tuple to split a collection. Awesome!

(split-with #(not (str/blank? %)) ["1" "2" "" "3"])
[("1" "2") ("" "3")]

Cool that is much cleaner than me doing it inline. :-) Okay now I just need to sum the backpack and use recursion to process the rest of the backpacks! I can use java to parse the strings in the backpack:

(map #(Integer/parseInt %) ["123" "456"])
(123 456)

Now I’ve got a sequence of numbers I can use reduce to sum them and we’re almost home!

(reduce + [1 2 3])
6

Finally I can put this all into a function and make a recursive call with the remaining input after I use rest to drop the empty string. I’ll add a if to check for the empty / base case and use conj to append the current backpack to the list of backpacks that’ll be returned from the recursive call. All together now!

(defn sum-backpacks [backpacks]
  (let [[current-backpack remaining-backpacks]
      (split-with #(not (str/blank? %)) backpacks)
	parsed-backpack (map #(Integer/parseInt %) current-backpack)
	current-total (reduce + parsed-backpack)]
      (if (empty? remaining-backpacks)
      [current-total]
      (conj
       (sum-backpacks (rest remaining-backpacks))
       current-total))))
(sum-backpacks ["1" "2" "" "1" "2" "3" "" "5"])
(sum-backpacks ["1" "" "7" "8" "" "4" "3"])
[5 6 3]
[7 15 1]

Cool that looks like it works now I just need to actually use it to solve the problem:

(defn part-1
  "finds highest calorie count of all backpacks"
  [backpacks]
  (apply max (sum-backpacks backpacks)))
(part-1 input)
73211

Neat! Lets put that into the AoC website and….

That’s the right answer! You are one gold star closer to collecting enough star fruit. [Continue to Part Two]

Part 2

Part 2 of the AoC tends to build on Part 1 so hopefully it’ll be pretty straight forward. Right what do I need to do?

Instead of the calorie total of the backpack with the most calories in it I now need to calculate the sum of calories in the three backpacks with the most calories in them.

Thankfully I can reuse the sum-backpacks function! Since sum-backpacks already returns the calorie total for all the backpacks I just need to manipulate that output a little. Lets see… For the three most calorie dense backpacks I’ll need to sort the list descending, then I can use take to get the first three backpacks then once again I can use reduce to sum them. Easy!

(defn part-2
  "sums the  calorie count of three highest calorie count backpacks"
  [backpacks]
  (reduce + (take 3 (sort > (sum-backpacks backpacks)))))
(part-2 input)
213958

And….

Both parts of this puzzle are complete!

Super! That was a fun way to spend some of my evening. I’ll be trying to keep up with the AoC as it progresses this year!

Thanks for reading!