Daniel Tull: Advent of Code

Day 1: Sonar Sweep

Advent of Code 2021

We’re back to saving Christmas this year! And this time to the depths of the ocean to retrieve the keys to Santa’s sleigh.

The swift-algorithms package comes in handy again, making even lighter of the simpler problems than they originally were. My initial solution turned the input list into a list of sliding windows of size 2 and counting the number of times when the first value in each window was less than the last.

try input.integers
    .windows(ofCount: 2)
    .map { try $0.first.unwrapped() < $0.last.unwrapped() }
    .count(of: true)

Part two is similar except we need to compare the rolling totals of three values at a time. I used the sliding windows algorithm again using a count of 3, then summing each sequence to find this rolling total, then used the same code as part one to find the amount of times where the prior total was less than the latter total.

try input.integers
    .windows(ofCount: 3)
    .map { $0.sum() }
    .windows(ofCount: 2)
    .map { try $0.first.unwrapped() < $0.last.unwrapped() }
    .count(of: true)

Readability Improvements

The first improvement I made was to use adjacentPairs instead of windows(ofCount: 2). This allows access to the first and last of the pairs without optionals.

input.integers
    .windows(ofCount: 3)
    .map { $0.sum() }
    .adjacentPairs()
    .map { $0.0 < $0.1 }
    .count(of: true)

Because of this change, the < operator can now just be passed to map.

input.integers
    .windows(ofCount: 3)
    .map { $0.sum() }
    .adjacentPairs()
    .map(<)
    .count(of: true)

This change actually reminded me that although count(where:) doesn’t exist in Swift yet, I had actually implemented it in my core Advent library.

input.integers
    .windows(ofCount: 3)
    .map { $0.sum() }
    .adjacentPairs()
    .count(where: <)

Finally, to remove the last of the curly braces, I changed the sum function I had added on Sequence to a property, which allowed me to access it via a KeyPath.

input.integers
    .windows(ofCount: 3)
    .map(\.sum)
    .adjacentPairs()
    .count(where: <)

What I like about this solution is that it reads very naturally like a set of rules derived from the puzzle itself.

Performance Improvements

199  A
200  A B
208  A B
210    B

The above is the example provided for part two and I later realised that the final comparison is between four values, the middle two of which exist in both sides, so can be eliminated. This allows us to use a four-measurement sliding window, and compare the first and last values.

try input.integers
    .windows(ofCount: 4)
    .count(where: { try $0.first.unwrapped() < $0.last.unwrapped() })

While in this puzzle the optimisation isn’t necessary, it does show some neat tricks that can be found in even these earlier days.