I had heard about Advent of Code for the first time in 2018, but had never participated. This year I’ve decided to give it a shot and document my solutions as I go. I started learning Common Lisp a few months ago so I will be attempting to solve the puzzles using that, at least until the difficulty surpasses my skill with the language.

If you’re curious, I am using SBCL (Steel Bank Common Lisp) and Visual Studio Code to do my development. I had started out with Emacs/SLIME since most people suggested that approach, but I ended up struggling to learn Lisp while also struggling to learn Emacs and it was just too much at once. While I’m sure it is an inferior Lisp development experience, it is meeting my needs for now.

The first puzzle introduces the plot for this years challenge: Santa is stranded at the edge of the solar system and needs your help to return to Earth in time for Christmas. That isn’t all that important but it does explain some of the puzzle themes.

The initial exercise requires us implement a simple algorithm for calculating the amount of fuel we need to launch each module based on its mass. The calculation is as follows:

Fuel required to launch a given module is based on its mass. Specifically to find the fuel required for a module, take its mass, divide by three, round down, and subtract 2.

We are then given an input file with the masses of each module. The answer is the sum of the fuel required for each.

First things first - loading the file. To be honest, I have no idea how to do this in Lisp. A quick trip to Stack Overflow reveals a solution, but I decided to go the easier route and use an existing library. The answer suggested uiop which appears to be provided as part of ASDF, a build tool in the Common Lisp community. This also gives us a chance to learn how to include a package using Quicklisp, a Common Lisp package manager.

Since I have already installed Quicklisp it is as simple as adding the following to the top of my file:

(ql:quickload "uiop")

From there we are able to define a function that reads each line of that file into a list of strings and then converts them into integers:

(defun get-masses ()
    (mapcar #'parse-integer (uiop:read-file-lines "input.txt")))

Next, We need to implement the algorithm itself. Common Lisp has an assortment of rounding function. In order to round down, we need to use the floor function:

(defun fuel-needed (mass)
    (- (floor (/ mass 3)) 2))

Finally, we can use mapcar to map the masses to their fuel requirements, and reduce to sum it all up:

(defun calculate ()
    (reduce #'+ (mapcar #'fuel-needed (get-masses))))

And that’s it! On to the second exercise of the day…

The follow up adds an additional requirement. Apparently our fuel calculation is incomplete since the fuel added to the rockets will increase the mass, requiring more fuel. In order to correct it we must take his into account:

So, for each module mass, calculate its fuel and add it to the total. Then, treat the fuel amount you just calculated as the input mass and repeat the process, continuing until a fuel requirement is zero or negative.

This will require some tweaks to our fuel-needed calculation. First off we’ll move our original calculation into a let form giving us a local variable (fuel-for-mass) to work with:

(let ((fuel-for-mass (- (floor (/ mass 3)) 2))) (...)

From there we can craft a condition that accomplishes the following: if the fuel required for the mass is 0 or negative, return 0, otherwise recursively call fuel-needed with the fuel’s mass. With that put together and combined with the let above, we get:

(defun fuel-needed (mass)
    (let (
        (fuel-for-mass (- (floor (/ mass 3)) 2)))
        (cond ((<= fuel-for-mass 0) 0)
              (t (+ fuel-for-mass (fuel-needed fuel-for-mass))))))

We can use the same get-masses and calculate function as the first exercise to get the answer to this puzzle.

Overall, pretty simple problems to solve, but they gave us a chance to learn a couple new things like how to load an external library using Quicklisp and how to use the floor function.

My full solution can be found on Github.