Advent of Code: Not Quite Lisp

This post is written as a set of Literate Commits. The goal of this style is to show you how this program came together from beginning to end.

Each commit in the project is represented by a section of the article. Click each section's header to see the commit on Github, or check out the repository and follow along.

Written by Pete Corey on Aug 17, 2016.

Christmas in August

Today’s an exciting day! We’ll be tackling an entirely new set of code katas using an entirely different language! We’ll be working on the first challenge in the Advent of Code series.

In the same vein as one of our recent posts, we’ll be using the Elixir language to solve this challenge.

This first commit is the result of mix new advent_of_code_01 and creates a base Elixir project.

config/config.exs

+use Mix.Config

lib/advent_of_code_01.ex

+defmodule AdventOfCode01 do +end

mix.exs

+defmodule AdventOfCode01.Mixfile do + use Mix.Project + + def project do + [app: :advent_of_code_01, + version: "0.1.0", + elixir: "~> 1.3", + build_embedded: Mix.env == :prod, + start_permanent: Mix.env == :prod, + deps: deps()] + end + + def application do + [applications: [:logger]] + end + + defp deps do + [] + end +end

test/advent_of_code_01_test.exs

+defmodule AdventOfCode01Test do + use ExUnit.Case + doctest AdventOfCode01 + + test "the truth" do + assert 1 + 1 == 2 + end +end

test/test_helper.exs

+ExUnit.start()

Watching Tests

Out of the box, Elixir comes with a fantastic unit testing framework. ExUnit can be run against our current project by running the mix test command. Unfortunately, this runs the test suite one time and quits. It doesn’t watch our project and rerun our suite when it detects changes.

Thankfully, the mix_test_watch dependency does exactly that. We can add it to our project, and run the mix test.watch command. Our test suite will rerun on every file change.

Now that our test suite is up and running, we’ll remove the example test Elixir provided for us.

mix.exs

... defp deps do - [] + [ + {:mix_test_watch, "~> 0.2", only: :dev} + ] end

test/advent_of_code_01_test.exs

... doctest AdventOfCode01 - - test "the truth" do - assert 1 + 1 == 2 - end end

Our First Test

To get things going, let’s write the simplest test we can think of. Amazingly, tests for Elixir functions can be written within the documentation for the function itself. For example, we can write our first test like this:


iex> AdventOfCode01.which_floor "("
1

Elixir will tease these tests out of the function docs and run them as part of our test suite.

We can make this first test pass by simply returning 1 from our new which_floor function:


def which_floor(directions) do
  1
end

And with that, our test suite flips back to green.

lib/advent_of_code_01.ex

defmodule AdventOfCode01 do + + @doc """ + Determines which floor Santa will end up on. + + ## Examples + + iex> AdventOfCode01.which_floor "(" + 1 + + """ + def which_floor(directions) do + 1 + end + end

Jumping Forward

Our next test is slightly more complicated:


iex> AdventOfCode01.which_floor "(("
2

Normally, we might take a bit more time to flesh out more naive, intemediary solutions, but this is a fairly easy problem to solve. We want to split our directions string into its component characters, map those characters into numbers (1 in this case), and then sum the result:


directions
|> String.split("", trim: true)
|> Enum.map(&handle_direction/1)
|> Enum.sum()

Our handle_direction private function expects to recieve a "(" as its input and always returns a 1:


defp handle_direction("("), do: 1

With those changes, our suite returns to green.

lib/advent_of_code_01.ex

... + iex> AdventOfCode01.which_floor "((" + 2 + """ def which_floor(directions) do - 1 + directions + |> String.split("", trim: true) + |> Enum.map(&handle_direction/1) + |> Enum.sum() end + defp handle_direction("("), do: 1 + end

Handling All Matches

Let’s add one last test that excercises the ability to process “down” directions:


iex> AdventOfCode01.which_floor("()(")
1

After adding this test, our suite fails. It complains that it can’t find a “function clause matching in AdventOfCode01.handle_direction/1”.

This is because we’re only handling “up” directions ("(") in the handle_direction function:


defp handle_direction("("), do: 1

Let’s add a new match for “down” directions:


defp handle_direction(")"), do: -1

After adding that new function definition to our module, our tests flip back to green. Victory!

lib/advent_of_code_01.ex

... + iex> AdventOfCode01.which_floor "()(" + 1 + """ ... defp handle_direction("("), do: 1 + defp handle_direction(")"), do: -1

Final Thoughts

Elixir is a beautiful language with lots of exciting features. In this example, we got a taste of transformation pipelines and how pattern matching can be used to write expressive control flow structures.

The first class treatment of documentation and testing is a welcome surprise coming from an ecosystem where testing seems to be an afterthought.

Around here we’re big fans of using small practice problems and code katas to learn new languages and paradigms. Expect to see Elixir making appearances in upcoming Literate Commit posts!