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:
forward X
increases the horizontal position by X units.down X
increases the depth by X units.up X
decreases the depth by X units.
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:
- down X increases your aim by X units.
- up X decreases your aim by X units.
- forward X does two things:
- It increases your horizontal position by X units.
- It increases your depth by your aim multiplied by X.
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).