# Advent of Code 2021 in Kotlin - Day 16

## Introduction

In Day 16 problem starts with a really long description. In my opinion, this type of tasks are the worst ones, as they come with a hard-to-understand problem, for which solution is really straightforward, but only when written in imperative approach with multiple nesting of code. Let’s see how we can approach this kind of problems in Kotlin by using context of the operation and lazy initialized variables in classes.

## Solution

Understanding the whole description of the task is the most difficult part of it. we should take our time, read it at leas twice and try to analyze given examples in practice. After that, we can go to actual implementation that contains a few fascinating concepts of Kotlin code.

We convert the given data from hex to bits using the String extension function toInt with specified radix and then extracting every of bits with divide and mod operations. It may look obvious but of course I started with explicitly specifying each case from task and came up with this nice approach after some refactorings.

private fun Char.toBits() = "$this".toInt(radix = 16).let { sequenceOf((it / 8) % 2, (it / 4) % 2, (it / 2) % 2, it % 2) }  There are two main features that allows us to have more concise and readable code which is pretty efficient in the same time. We use sequences to consume input data from bits. That’s why we define some helper function that is capable of “consuming” some beginning of the sequence and returning the rest of the sequence in the single call private inline fun <T> Sequence<T>.use(n: Int, action: (Sequence<T>) -> Unit): Sequence<T> { sequence { yieldAll(take(n)) }.let(action) return drop(n) }  It’s important to highlight how the lambda scope (closure) in Kotlin works to get full understanding of the usage of this extension function. Remember, that in case of Java, when we want to capture some variable in lambda scope, then it has to be effectively final which means, that it cannot be reassigned. For example, we cannot write in Java the definition like private static int sum(int to) { int idx = 0; final int result = IntStream.iterate(1, (it) -> { idx += 1; // this will not compile as idx has to be final return it + 1; }).limit(to).sum(); System.out.println(idx); return result; }  On the other hand, in Kotlin we can write some similar code that will compile and work as expected. private fun sum(to: Int): Int { var idx = 0 val result = generateSequence(1) { idx += 1 it + 1 }.take(to).sum() println(idx) return result }  This type of approach is more complex in runtime and may be slower but gives the developer more possibilities to write some functionalities. If we look at the bytecode that is generated for such implementation we would see that the variable is stored as IntRef, so the object which field is modified. If we would like to make it work in Java, we would have to create our own class that would be capable of wrapping some value, then wrap the value and handle it properly inside the lambda scope. All these steps would introduce a lot of noise to our code, so we should be really thankful to Kotlin compiler that it does the job for us. When we know that, we can take a look at the whole implementation, where we can find updates of some vars inside the lambda of use extension function. E.g. when we want to consume the packet version value, we consume the first 3 bits and use it to update the value of version field from PacketHeader, by simply saying from.use(3) { version = it.msb().toInt() }  In the following code we can find even more complex update of such variables in scope of the lambda, that in my opinion produced really readable code. ### Day16.kt import kotlin.properties.Delegates.notNull object Day16 : AdventDay() { override fun solve() { val data = reads<String>()?.singleOrNull() ?: return val bits = data.asSequence().flatMap { it.toBits() } val (packet, rest) = buildPacket(from = bits) rest.requireZeros() packet.sumVersionNumbers().printIt() packet.eval().printIt() } } private typealias Bits = Sequence<Int> private fun Packet.sumVersionNumbers(): Int = when (this) { is NumberPacket -> header.version is OpPacket -> header.version + subPackets.sumOf { it.sumVersionNumbers() } } private fun Packet.eval(): Long = when (this) { is NumberPacket -> value is OpPacket -> when (header.type) { 0 -> subPackets.fold(0L) { acc, p -> acc + p.eval() } 1 -> subPackets.fold(1L) { acc, p -> acc * p.eval() } 2 -> subPackets.minOf { it.eval() } 3 -> subPackets.maxOf { it.eval() } 5 -> subPackets.let { (l, r) -> if (l.eval() > r.eval()) 1 else 0 } 6 -> subPackets.let { (l, r) -> if (l.eval() < r.eval()) 1 else 0 } 7 -> subPackets.let { (l, r) -> if (l.eval() == r.eval()) 1 else 0 } else -> throw IllegalStateException("Unknown combination of data in packet:$this")
}
}

private fun buildPacket(from: Bits): Pair<Packet, Bits> = PacketHeader().run {
val bits = from
.use(3) { version = it.msb().toInt() }
.use(3) { type = it.msb().toInt() }

when (type) {
4 -> buildNumberPacket(from = bits)
else -> buildOpPacket(from = bits)
}
}

var bits = from
bits = bits.use(1) { reading = it.first() == 1 }
bits = bits.use(4) { value = value * 16 + it.msb() }
}
Pair(this, bits)
}

var bits = from
.use(1) { countSubPackets = it.first() == 1 }
.use(if (countSubPackets) 11 else 15) { subPacketsCounter = it.msb().toInt() }
subPackets = if (countSubPackets)
buildList {
repeat(subPacketsCounter) {
val (subPacket, subBits) = buildPacket(from = bits)
add(subPacket).also { bits = subBits }
}
}
else buildList {
bits = bits.use(subPacketsCounter) {
bits = it
while (bits.any()) {
val (subPacket, subBits) = buildPacket(from = bits)
add(subPacket).also { bits = subBits }
}
}
}
Pair(this, bits)
}

private fun Char.toBits() = "\$this".toInt(radix = 16).let {
sequenceOf((it / 8) % 2, (it / 4) % 2, (it / 2) % 2, it % 2)
}

private inline fun <T> Sequence<T>.use(n: Int, action: (Sequence<T>) -> Unit): Sequence<T> {
sequence { yieldAll(take(n)) }.let(action)
return drop(n)
}

private fun Bits.msb() = fold(0L) { acc, b -> 2 * acc + b }

var version: Int by notNull()
var type: Int by notNull()
}

var value: Long = 0
}

var countSubPackets: Boolean by notNull()
var subPacketsCounter: Int by notNull()
var subPackets: List<Packet> by notNull()
}

private fun Bits.requireZeros() =
if (any { it == 1 }) throw IllegalStateException("Left non zero bytes") else Unit


## Extra notes

In our code we used Kotlin delegated properties multiple time. We define field in classes with e.g.

var countSubPackets: Boolean by notNull()


to have the instance of the class with field, which is not initialized after construction, but can be accessed as not nullable value, when initialized. That’s because Kotlin delegation properties can implement some interface when storing other value in practice. In this example we have a delegated property, which stores a nullable value, but expose it as not nullable. When the value is accessed and is not set, then the exception is thrown. It’s worth to see how this works and play with it, as it allowed us to create some hind of scope, in which we create the instance of PacketHeader and initialize its fields later, without creating any builder for it.

###### Student of Computer Science

My interests include robotics (mainly with Arduino), mobile development for Android (love Kotlin) and Java SE/EE applications development.