Advent of Code 2021 in Kotlin - Day 2

Publish date: Dec 14, 2021
Tags: kotlin aoc

It’s the 14th and I’m just writing about day 2, which is pretty much what I expected to happen. If the trajectory is linear it’s not too bad, I’ll at least be done in time for AOC 2022.

Day 2’s problem has us read commands and update our position with the following rules:

and then returning the product of horizontal and depth.

Given the following commands, we’d end up with a horizontal value of 15 and depth of 10, and the product will be 150

forward 5
down 5
forward 8
up 3
down 8
forward 2

The command looks like a nice opportunity for me to have a go at algebraic data types (ADTs) in Kotlin. The way I’ve read that people do this is to create an interface or sealed class and then create a class for each field. I’m not sure if there’s a reason to use interfaces over sealed classes, or vice versa. I tried both and they both worked.

interface Command
data class Forward(val distance: Int) : Command
data class Down(val distance: Int): Command
data class Up(val distance: Int): Command

Let’s parse the input:

fun parse(commands: List<String>): List<Command> {
    return commands.map { c ->
        val (command, distance) =
            c.split(" ")
            .let { (c, d) -> Pair(c, d.toInt())}
        
        when (command) {
            "forward" -> Forward(distance)
            "down" -> Down(distance)
            "up" -> Up(distance)
            else -> throw Exception("Invalid command")
        }
    }
}

I’m going to return a Position instead of the product, as that’s what I’d do in real life, let the caller decide what to do with the calculation.

data class Position(val horizontal: Int, val depth: Int)

fun getPosition(commands: List<Command>): Position {
    var horizontal = 0
    var depth = 0

    for (c in commands) {
        when (c) {
            is Forward -> horizontal += c.distance
            is Down -> depth += c.distance
            is Up -> depth -= c.distance
        }
    }

    return Position(horizontal, depth)
}

Putting it all together:

fun run(commandData: List<String>): Int {
    return parse(commandData)
        .let { getPosition(it) }
        .let { p -> p.horizontal * p.depth }
}

In part 2 we learn that we read the manual incorrectly and that we not only need to keep track of horizontal and depth, but also aim. The rules are:

We only need to change the getPosition function to satisfy these rules, the rest stays as it is.

fun getPosition(commands: List<Command>): Position {
    var horizontal = 0
    var depth = 0
    var aim = 0

    for (c in commands) {
        when (c) {
            is Forward -> {
                horizontal += c.distance
                depth += aim * c.distance
            }
            is Down -> aim += c.distance
            is Up -> aim -= c.distance
        }
    }

    return Position(horizontal, depth)
}

Thanks for reading! Please feel free to send me an email to talk more about this (or anything else for that matter).