Jonathan Sumner Evans
Sumner Evans
Software Engineer at Automattic working on Beeper

Advent of Code 2023

Written by Human, Not by AI

Every year since 2015, Eric Wastl creates a two-part programming problem for each of the 25 days of Advent. He publishes a new problem every day at adventofcode.com at exactly midnight EST which is 22:00 the day before for me in MST. The last two years, I decided to do the problems as soon as they came out and streamed my problem solving sessions on my Twitch channel and uploaded them to my Youtube channel. The last two years I updated a blog post about each of the days, and I will try and do that this year as well.

This year, I’m leaving my fate up to chance by spinning a wheel every night which will dictate what language I use for that night.

The random spinner wheel which will decide my fate every night

Spinner wheel which will decide my fate every night

The random spinner wheel which will decide my fate every night

I’m going to be streaming some of my solves and I will try and keep this blog post up-to-date every day with my thoughts on each problem.

Summary of Results

The following are my results across all of the days.

      -------Part 1--------   --------Part 2--------
Day       Time  Rank  Score       Time   Rank  Score
  7   01:11:56  8044      0   02:06:29   8720      0
  5   01:14:02  9429      0          -      -      -
  2   00:44:29  9493      0       >24h  94541      0
  1   00:11:38  5636      0   00:21:47   2021      0

Language statistics:

$ tokei -e '*.txt' -e '*.json'
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 Batch                   1           92           71            0           21
 F#                      3           78           63            5           10
 Kotlin                  4          171          115           31           25
 MSBuild                 4           44           38            0            6
 OCaml                   2          229          189           19           21
 Shell                   1          249          106          117           26
 TypeScript              1           48           44            0            4
===============================================================================
 Total                  16          911          626          172          113
===============================================================================

Day 1: Trebuchet?!

Link:https://adventofcode.com/2023/day/1
Solutions:TypeScript
Part 1:00:11:38, 5636th
Part 2:00:21:47, 2021st
 Advent of Code 2023 - Day 1 | Random Wheel Spin (*5636, **2021)

Today I spun TypeScript! I spent most of my time figuring out how to read from stdin in Node. There was a lot of DuckDuckGo-ing involved.

I ended up finding how to use the readline.createInterface and adding a line listener which added all of the lines to an array and a close listener which did the actual computation.

Part 1

In order to find all of the digits, I used .filter to iterate through the list of characters and used a regex for determining whether or not it was a digit.

Then I took the first and last element of the list and then just concatenated them together as a string and then used parseInt.

Part 2

For part 2, you now have to also take into account spelled-out numbers like one, two, etc.

I didn’t realize that they could overlap, so I wasted a couple of submissions on that.

I did get to abuse the JavaScript type system a bit to construct the final number:

return parseInt("" + ints[0] + ints[ints.length - 1]);

Day 2: Cube Conundrum

Link:https://adventofcode.com/2023/day/2
Solutions:F#
Part 1:00:44:29, 9493th
Part 2:>24h, 94541st
 Advent of Code 2023 - Day 2 | Random Wheel Spin (*9493, **94541)

Today I spun F#! It took me a long time to just install and learn how to read input in F#, and then it took an even longer time to solve part 1 since I wasn’t really sure what functions I could even use. F# has some constructs that are similar to other functional languages that I’ve used in the past, which helped the learning curve.

I was hampered by not having syntax highlighting and a language server. I’ll have to figure that out before I spin F# again.

I don’t think that I did anything super novel today, so I’m not going to make comments about either of the parts.

Day 7: Camel Cards

Link:https://adventofcode.com/2023/day/7
Solutions:OCaml
Part 1:01:11:56, 8044th
Part 2:02:06:29, 8720th
 Advent of Code 2023 - Day 7 | Random Wheel Spin (*8044, **8720)

Today I spun OCaml! It took a very long time for me to get into functional programming mode. The first task was to parse the hand into a tuple of (hand_type, hand_values) where hand_values was just a tuple of integers representing the card values.

In order to do that, I sorted the characters in the hand and then compressed them into a sorted list of (count, character) tuples. Then, I pattern matched on the different possible combinations of counts. For example, if you have AA233, then it would sort into:

[(1, '2'); (2; 'A'); (2, '3')]

which I was able to pattern match with:

match hand with
  ...
  | [_; (2, _); (2, _)] -> (TwoPair, ...)

It took me a very long time to figure out how to do it properly, but eventually I was able to get it.

I had a really horrible part 2 delta because my pattern matching-foo was not very good, and I missed a couple of cases that forced me to just debug through each hand one at a time.

Day 8: Haunted Wasteland

Link:https://adventofcode.com/2023/day/8
Solutions:F#
Part 1:00:20:16, 5859th
Part 2:00:42:07, 2970th

Today I spun F#. It was quite fortunate, because this problem was inherently pretty recursive in nature. Trees problems are normally very amenable to recursive thinking. The problem for me was that I couldn’t recursively think.

For part 1, I got away with a non-tail-recursive function. I’m not sure if the non-tail-recursive approach works for part 2 or not, but in my attempts to optimize part 2 I converted to use a tail-recursive approach which was sufficiently space-efficient. This is the core of the algorithm. I think it’s quite clean.

let rec step_p1 current directionIdx =
    if current = "ZZZ" then
        directionIdx
    else
        let (left, right) = map[current]

        match directions[directionIdx % directions.Length] with
        | 'L' -> (step_p1 left (directionIdx + 1))
        | 'R' -> (step_p1 right (directionIdx + 1))
        | _ -> failwith "invalid direction"

printfn "%A" (step_p1 "AAA" 0)

Update: as you can tell from the lack of posts, I decided to give up on this year. I had a very busy early December due to lots of grading finishing up the fall semester as well as a quite busy time at work (we released Beeper Mini).

I’m hoping that next year I’ll be able to get back to this and spin the wheel again!