Skip to Content

Advent of Code 2023 - Day 6, in Kotlin - Wait For It

Kotlin solutions to parts 1 and 2 of Advent of Code 2023, Day 6: 'Wait For It'

Posted on

What a fun problem! I’m sure there’s some clever quadratic math one could apply to get an answer without doing any kind of looping at all, but I’m happy with what I did here.

If you’d rather just view the code, my GitHub Repository is here.

Puzzle Input

We will take our puzzle input in as a List<String> and define it as a private property because each part of the puzzle requires separate parsing.

class Day06(private val input: List<String>) {

}

Running The Race

Instead of following the puzzle text, let’s think about what we expect to see. There are some set of races where we don’t wait long enough that we expect to not set a record. Correspondingly, at the other end of the spectrum there is another set of races where we wait too long. Rather than scanning the entire possible range of races, let’s come at this from both ends.

For example:


-------------------------time---------------------------->
sssssssssssssRRRRRRRRRRRRRRRRRRRRRRRRRRRRRssssssssssssssss 
--scan up--->                             <---scan down---

Race Results
--------------
s = Too Slow
R = New Record

We will scan UP from 1 second (since 0 makes no sense, we can’t ever set a record that way) and find the first race we set a record for. Then we scan DOWN from the highest time until we set a record. The difference between these numbers (minus one, see below) is our answer.

// In Day06

private fun race(time: Long, distance: Long): Long {
    val start = (1 .. time).first { hold ->
        ((time-hold)) * hold > distance
    } -1

    val end = (time downTo 1).first { hold ->
        ((time-hold)) * hold > distance
    }

    return end-start
}

In order to run a race we take in the time and distance record. We’ll do all of this with Long instead of Int because the numbers get longer in part 2. As stated in the strategy above, we set up a range to look at times starting at 1 and take the first one where there is a record. Because we want to only count non-winners, we need to subtract by 1 here. The inner calculation figures out the distance we travel given a hold time, and measures it against the distance record.

The second range works the same way, except we go backwards from the end of the time period to the beginning, finding the first race where we set a record.

⭐ Day 6, Part 1

The puzzle text can be found here.

Other than parsing, the race function does all of our work for us!

// In Day06

fun solvePart1(): Long {
    val times = input.first().substringAfter(":").split(" ")
                     .filter { it.isNotEmpty() }.map { it.toLong() }
    val distances = input.drop(1).first().substringAfter(":").split(" ")
                         .filter { it.isNotEmpty() }.map { it.toLong() }

    return times.zip(distances)
        .map { race(it.first, it.second) }
        .reduce(Long::times)
}

Parsing is done like in days previous - take the substringAfter the :, split it by space, remove anything that is empty, and map to Long. The only difference between parsing times and distances is dropping the first row of input.

In order to run the races and calculate the product, we will zip the times and distances together. For each pair of them, this will give us a Pair<Long,Long> where the first element is the time and the second element is the distance. We could probably iterate over one of these lists and look up the other by index, but I like this better.

We run each race and then using reduce, we multiply the results together via the Long::times function.

Running this gives us our answer.

Star earned! Onward!

⭐ Day 6, Part 2

The puzzle text can be found here.

Again, since race does all the work we need, we can parse our input and run it directly.

// In Day06

fun solvePart2(): Long {
    val time = input.first().substringAfter(":")
                    .filter { it.isDigit() }.toLong()
    val distance = input.drop(1).first().substringAfter(":")
                        .filter { it.isDigit() }.toLong()

    return race(time, distance)
}

Parsing is very similar to part 1 except we filter out anything that isn’t a digit. The race function is run as-is and gives us our answer quickly.

Star earned! See you tomorrow!

Further Reading

  1. Index of All Solutions - All posts and solutions for 2023, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 6
  4. Advent of Code - Come join in and do these challenges yourself!