Skip to Content

Advent of Code 2019 - Day 8, in Kotlin

Kotlin solutions to parts 1 and 2 of Advent of Code 2019, Day 8: 'Space Image Format'

Posted on

Today is a nice change of pace from yesterday’s more complicate puzzle. I wonder if SIF (Space Image Format) has some weirdo pronunciation like GIF does?

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

Problem Input

The file we’re given is a very long String, which is what we’ll pass into our class for the day. We won’t do anything else with it yet.

class Day08(private val input: String) 

Since we’re given some constants, let’s define them in our companion while we’re here.

// In Day08

companion object {
    private const val width = 25
    private const val height = 6
    private const val lineLength = width * height
}

⭐ Day 8, Part 1

The puzzle text can be found here.

I think this calls for our friend chunked. The chunked function splits an iterable (such as a String) up into parts. In our case, we are going to split our String input up into Strings that are 125 characters long (lineLength) so that each chunk/String represents one layer of the image.

fun solvePart1(): Int =
    input
        .chunked(lineLength)
        .map { layer -> layer.groupingBy { it }.eachCount() }
        .minBy { it.getOrDefault('0', 0) }
        ?.let { it.getOrDefault('1', 0) * it.getOrDefault('2', 0) }
        ?: throw IllegalStateException("Image Corrupted: Check Digital Sender Network Integrity")

Once we have our layers, we can use groupingBy, which takes a lambda expression for the grouping. Because we only want the characters themselves as the key to the map we’ll end up with, we just pass it to groupingBy. Since the object that groupingBy has an eachCount function which counts the number of values in each group, we’ll take advantage of that. At this point we have a List<Map<Char, Int>>. The List is our layers, and the Map is the count of each symbol on that layer.

The next step is to go through all of the layers and find the one with the fewest 0s in it. We use .minBy on List to do that. Because minBy returns a nullable (an empty list doesn’t have a minimum), we use the safe traversal operator and let to do the rest of the work. We have our target layer, so we find the count of 1s and multiply it by the count of 2s, which is our answer.

Because we’ve been ignoring our nulls this whole time, we throw an exception at the end. In Advent of Code we know there won’t be a null and we could use the Hold-My-Beer Operator (!!) to assert non-null along the way, but I don’t like those.

Running this solves Part 1! Star earned!

⭐ Day 8, Part 2

The puzzle text can be found here.

This sounds like fun. Because we’re going to end up using chunked again to break our input String into layers, we might as well define an extension function:

private fun List<String>.pixelAt(at: Int): Char =
    if(map { it[at] }.firstOrNull { it != '2' } == '1') '#' else ' '

Given an index (at), we want to paint either a # or a space. I realize the problem description has more “colors” than that, but I know the answer and we didn’t end up needing any more than two printed symbols. To do this, we examine all of the Strings for what is at the index specified. Because the layers are in top-down order, we take the firstOrNull element that is not 2 (transparent). If that number is a 1, we draw a #, otherwise, a space.

We’ll use this function to solve Part 2:

fun solvePart2() {
    val layers: List<String> = input.chunked(lineLength)
    (0 until lineLength)
        .map { layers.pixelAt(it) }
        .chunked(width)
        .forEach {
            println(it.joinToString(separator = ""))
        }
}

Similar to Part 1, we’ll use chunked on our input String to create a List<String>, where each element in the list represents a layer. For every pixel, we’ll use our pixelAt function to determine what pixel should be drawn by examining all of the layers at that pixel’s point. Once we have that, we can chunk our composite line into rows and columns using chunked again. All that’s left is to print our image with the help of joinToString.

I’m a massive fan of Advent of Code, but I’ll confess I do not really like problems like this that require us to visually interpret an answer. I’ve worked with a few vision impaired developers and I feel that problems like this might make solving this more difficult for them. Plus, they are hard to write unit tests for.

If we run this code and interpret the results, we solve Part 2 and earn our 2^4th star!

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 8
  4. Advent of Code - Come join in and do these challenges yourself!