Advent of Code 2023 part 1
Julia or Elixir?
[2023-12-03]#development #adventofcode #julia #elixir
Next: Advent of Code 2023 part 2
Advent of Code 2023 is here! This year I thought I was going to go back to Julia, but after solving day 1 I decided that instead this would be a great opportunity to try out Elixir, which I’ve been meaning to play with for a while; I’d done some exloration with Erlang years ago but heard Elixir was a bit more approachable.
After starting with a standalone Elixir Script, I moved to a full project to support dependencies and unit tests, under my omnibus repository
Advent of Code problems really have four parts:
- Read and grok the problem statement
- Parse the input
- Solve part 1
- Solve part 2
In the past I’ve mainly relied on regex to handle part 2; this year I’m trying to use parser-combinators instead, which Elixir has a wonderful library for in NimbleParser.
This is such an amazing improvement over simple regex, allowing for powerful, semantic parsing, that may be more verbose than regex but is so much more extensible and readable. Consider the rules that convert an example schematic from day 3:
467..114..
617*......
into
[
[row: 0, col: 0, number: 467],
[row: 0, col: 5, number: 114],
[row: 1, col: 0, number: 617],
[row: 1, col: 3, symbol: ?*]
]
Here’s the code to do that with NimbleParsec:
defparsec(
:parse_schematic,
choice([
# integer() does not match sign so that works ok for us, since it seems
# part numbers cannot be negative
integer(min: 1) |> unwrap_and_tag(:number),
ignore(string(".")),
ignore(string("\n")),
choice([
ascii_char([0..?0]),
ascii_char([(?0 + 1)..(?A - 1)]),
ascii_char([(?z + 1)..256])
])
|> unwrap_and_tag(:symbol)
])
|> pre_traverse(:tag_location)
|> repeat
)
defp tag_location(rest, [], context, _, _) do
{rest, [], context}
end
defp tag_location(rest, args, context, {line, line_offset}, offset) do
{rest, [[row: line - 1, col: offset - line_offset] ++ args], context}
end
In general Elixir pipelines make for such ergonomic code (cf. Julia pipelines). Multiple dispatch, destructuring, and pattern matching are some other fantastic traits on display here, which bring in some of my favorite parts of Clojure and make for a great developer experience.
Next: Advent of Code 2023 part 2