Advent of Code 2017 - Day 15, in Kotlin
Kotlin solutions to parts 1 and 2 of Advent of Code 2017, Day 15: 'Dueling Generators'
On Day 15 we will show how awesome Kotlin’s sequence generators are and earn two easy stars! If you’d rather jump straight to the code, it’s right here .
I’ve challenged myself to post each day’s solution to Github and blog about it. Like last year, I’m going to be solving these problems in Kotlin. I might go back and revise solutions, but I’ll be sure to annotate that fact in these posts.
Problem Input
We are given a set of input
for this problem. I’ve loaded this into a List<String>
called input
, which we will do some further parsing on later.
⭐ Day 15, Part 1
The puzzle text can be found here.
The second I saw this challenge I knew exactly how I would solve it. Kotlin has a function that allows you to build a sequence called generateSequence
. Give it a starting point and a lambda to describe your iteration, and it’s done. Here’s the one I wrote to handle both Generator A and Generator B:
private fun generator(start: Long, factor: Long, divisor: Long = 2147483647): Sequence<Short> =
generateSequence((start * factor) % divisor) { past ->
(past * factor) % divisor
}.map { it.toShort() }
It takes the start
point, the multiplication factor
, and the divisor
and calculates the answer. The first time through it uses the start value, and every other time it uses the most recently generated number (past
) as the start value. In this specific case I’m downcasting it to a Short
, which is conveniently 16 bits. Sure, we could do some bitwise and-ing, but I felt that for a simple problem like this it was clearer to just downcast. Plus, we finally have a use case for Short
on the JVM. :)
Aside: Something I like doing with Kotlin is to not hard code magic numbers except as default parameter values. That way people can override them but if they don’t they still act like magic numbers.
All we need to do now is parse our input, and call our generators.
private val notNumbers = """[^\d]""".toRegex()
private val generatorA = generator(input[0].replace(notNumbers, "").toLong(), 16807)
private val generatorB = generator(input[1].replace(notNumbers, "").toLong(), 48271)
fun solvePart1(pairs: Int = 40_000_000): Int =
generatorA
.zip(generatorB)
.take(pairs)
.count { it.first == it.second }
The part that I like about this solution is the use of zip
which takes two Sequences
and jams the results together into a Pair
. We use take
to limit how many pairs we look at and then count the ones where both of the generators crank out the same values.
Easy and done for star number one.
⭐ Day 15, Part 2
The puzzle text can be found here.
This solution is virtually similar to the one we just wrote except we filter the results down. We’ll use the same generators and approach otherwise:
fun solvePart2(pairs: Int = 5_000_000): Int =
generatorA.filter { it % 4 == 0 }
.zip(generatorB.filter { it % 8 == 0 })
.take(pairs)
.count { it.first == it.second }
See? More or less the same except for the filters. An easy second star for today.
I hope you’ve learned something and as always, feedback is welcome!
15 days and we’ve earned 30 stars with 20 left to go! 3/5 of the way
Further Reading
- Index of All Solutions - All solutions for 2017, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 15.
- Advent of Code - Come join in and do these challenges yourself!
- Music to Code By - Dueling Banjos, by Eric Weissberg.