Skip to Content

Advent of Code 2019 - Day 4, in Kotlin

Kotlin solutions to parts 1 and 2 of Advent of Code 2019, Day 4: 'Secure Container'

Posted on

Today’s puzzle has quite a few solutions if you’re willing to get creative. I went with one that largely involves the Kotlin Standard Library, which continues to impress me with how many useful functions it has!

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

Problem Input

We’re given a string of text representing a range of integers for our input. The class for today’s solution (which I drive via unit tests) takes in an IntRange that has already been parsed.

class Day04(private val range: IntRange) 

While this can be inserted manually, here’s how I parsed the String into an IntRange in my test:

private val inputRange = resourceAsString("day4.txt").split("-").let {
    it[0].toInt()..it[1].toInt()
}

⭐ Day 4, Part 1

The puzzle text can be found here.

One thing that struck me about the way this problem description is written is that it goes out of its way to say “each number is sorted”. Once I realized this, I figured there were a few ways to solve it. The way I picked was to turn each of our candidate Ints into a String and use a combination of zipWithNext and all to see if the characters are in order:

private fun isSorted(input: String): Boolean =
    input.zipWithNext().all { it.first <= it.second }

The zipWithNext function comes in handy when comparing adjacent elements in a String or a List (I’m simplifying this concept here). Essentially, it turns each adjacent pair of elements into a Pair. For example, if we have the string “Todd”, and we call "Todd".zipwithNext(), we end up with several pairs in a List: Pair("T", "o"), Pair("o", "d"), and Pair("d", "d"). Neat, huh? If you’ve ever used windowed from the standard library to do the same thing, this is similar except that zipWithNext returns a List<Pair<T, T>> instead of a List<List<T>>.

Once we have our data in that format, we can make sure that all of the Pairs have their elements in the right order (either they match, or the first element comes before the second element).

Another way to do this would be to turn our String into a CharArray, sort it, and then compare the results with a CharArray version of the original. But I felt that zipWithNext and all was cleaner and easier to read (for me).

Next, we need to make sure our candidate String contains two of the same number somewhere within it. As we’ve just discovered, we can assume that our String is sorted at this point in the calculation. This allows us to use zipWithNext again, but instead of all, we want to know if any elements are identical:

private fun containsMatchingPair(input: String): Boolean =
    input.zipWithNext().any { it.first == it.second }

Combining these two functions is all we need to solve part 1:

fun solvePart1(): Int =
    range
        .map { it.toString() }
        .count { isSorted(it) && containsMatchingPair(it) }

Take the range, convert each element to a String and count the number of times we find a String that is sorted and contains a pair.

Part 1 done! Star earned!

⭐ Day 4, Part 2

The puzzle text can be found here.

Thankfully we’re pretty close to having Part 2 done, we just need to write one more helper function. In this case, we need to know if any number appears in the string exactly twice. We can’t reuse containsMatchingPair because it can’t tell the difference between matching two in a row and three in a row.

Once again, we’ll go to the Kotlin Standard Library to find a solution - groupBy to the rescue:

private fun containsIsolatedPair(input: String): Boolean =
    input.groupBy { it }.any { it.value.size == 2 }

In this case we can call groupBy on our String, which groups by each Char in the String. Then we use any to determine if any group contains exactly two elements, meaning there is a pair that is not part of a bigger set of identical items.

It’s worth noting we could re-implement contanisMatchingPair from Part 1 with this approach:

private fun containsMatchingPair(input: String): Boolean =
    input.groupBy { it }.any { it.value.size >= 2 }

See the difference? We want a group whose size is at least 2 rather than exactly 2. Both versions of containsMatchingPair give the same results, it’s just up to you to decide which is clearer to you.

Now that we have all we need, we can solve Part 2:

fun solvePart2(): Int =
    range
        .map { it.toString() }
        .count { isSorted(it) && containsIsolatedPair(it) }

It looks pretty similar to Part 1’s solution, doesn’t it? There might be more efficient ways to solve today’s problems, but I feel that these are clear and show off the power of the Kotlin Standard Library.

Part 2 solved, and star earned!

Further Reading

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