Advent of Code in Kotlin 2021 - Day 16
This is part 16 of a series, so if you haven't read the previous parts, start here.
For today's puzzle we are given a data package as binary data encoded in hex. Each package contains either a literal value or contains subpackages and an operation that should be performed on them. The challenge is mainly in parsing the packages.
I was a bit worried seeing the times on the leaderboard, but it actually didn't take me too long - just went through the requirements at a steady pace without making any costly mistakes. Not much time to clean up the code, but I'm feeling ok about it.
fun main() {
val binary = inputText(2021, 16).toBinary()
val topLevelPacket = parse(binary).packet
println(topLevelPacket.totalVersionIds)
println(topLevelPacket.value)
}
private fun parse(binary: String): ParseResult {
var remaining = binary
val version = remaining.take(3).toInt(2).also { remaining = remaining.drop(3) }
val typeId = remaining.take(3).toInt(2).also { remaining = remaining.drop(3) }
if (typeId == 4) {
var tempNumber = ""
while (remaining.first() == '1') {
tempNumber += remaining.drop(1).take(4)
remaining = remaining.drop(5)
}
tempNumber += remaining.drop(1).take(4)
remaining = remaining.drop(5)
return ParseResult(LiteralPacket(version, typeId, tempNumber.toLong(2)), remaining)
} else {
val subPackets = mutableListOf<Packet>()
val lengthType = remaining.take(1).toInt(2).also { remaining = remaining.drop(1) }
if (lengthType == 0) {
val length = remaining.take(15).toInt(2).also { remaining = remaining.drop(15) }
var subPacketBinary = remaining.take(length).also { remaining = remaining.drop(length) }
while (subPacketBinary.isNotEmpty()) {
val (subPacket, remainingSubPacketBinary) = parse(subPacketBinary)
subPacketBinary = remainingSubPacketBinary
subPackets.add(subPacket)
}
} else {
val subPacketCount = remaining.take(11).toInt(2).also { remaining = remaining.drop(11) }
while (subPackets.size < subPacketCount) {
val (subPacket, nextRemaining) = parse(remaining)
remaining = nextRemaining
subPackets.add(subPacket)
}
}
return ParseResult(OperatorPacket(version, typeId, subPackets), remaining)
}
}
private data class ParseResult(val packet: Packet, val remaining: String)
private interface Packet {
val totalVersionIds: Int
val value: Long
}
private data class LiteralPacket(val version: Int, val typeId: Int, override val value: Long): Packet {
override val totalVersionIds: Int
get() = version
}
private data class OperatorPacket(val version: Int, val typeId: Int, val subPackets: List<Packet>): Packet {
override val totalVersionIds: Int
get() = version + subPackets.sumOf { it.totalVersionIds }
override val value: Long
get() {
return when(typeId) {
0 -> this.subPackets.map { it.value }.reduce { a, b -> a + b}
1 -> this.subPackets.map { it.value }.reduce { a, b -> a * b}
2 -> min(this.subPackets.map { it.value })
3 -> max(this.subPackets.map { it.value })
5 -> if (this.subPackets.first().value > this.subPackets.last().value) 1 else 0
6 -> if (this.subPackets.first().value < this.subPackets.last().value) 1 else 0
7 -> if (this.subPackets.first().value == this.subPackets.last().value) 1 else 0
else -> throw IllegalArgumentException("Unknown operator $typeId")
}
}
}
private fun Char.toBinary() = this.toString().toInt(16).toString(2).padStart(4, '0')
private fun String.toBinary() = this.map(Char::toBinary).joinToString("")
Two more stars in the bag.