Christoph Wende

About Profession

Advent Of Code 2023 - Learning & Failing

2023-12-01
Advent Of Code 2023 - Learning & Failing

It’s this time of the year again

As usual, developers around the world celebrate the last month of the year, by participating in the only coding challenge combining christmas & code in somewhat relaxing way.

I guess, we all remember the carnage of 2021, don’t we?

Anyway, beside having already implemented a few projects in Kotlin, I’ll try to finish this year’s Advent Of Code by learning all the dirty tricks in this neat language. Let’s see at which day the rage-quit becomes a reasonable option.

Day 1-3 - Warm Up

Seems ok, I’m warming up with the language again, a colleague gives me an insightful review of my code,
I’m getting used to the STL. Though, I had a strange bug in day 3’s solution, a rewrite of the code solved my issue and even resulted in less but more understandable code. A successful refactoring, but to be honest, I won’t trust this comfortable feeling of

Everything is fine, I know what I do 😃

These have been famous last words more than once …

Day 4

Wow, that was straight forward, simple object-oriented approach, a little regex sprinkled in here and there, nothing to fancy.
I like where this is going!

Day 5

I need a rest, this took me a while.
Also, this took my machine a while.

I’ll update as soon as I fixed the bug, causing my result to be one off. Perhaps I should also come up with a better solution, especially in regard to memory consumption.

Day 6

Again, like day 4, this challenge was quite easy. I have the feeling that the stretch-goal of this and also yesterday’s task is mostly optimizing the calculation of huge numbers. Anyway, Kotlin and the underlying JVM seems to handle it pretty well up till now.

Day 7

To be honest, I really struggled on that. In particular, I had a tricky bug in the detection of winning hands in combination with jokers.

My initial implementation counted multiple occurrences of cards in each hand, sorted descending and regarding only the top resp. top two counts; then adding the joker count to generate the score. With this logic, a pair would be found by adding the first count plus the amount of jokers, but it had an obvious flaw: Jokers are also counted, which results in a completely wrong score calculation, i.e.:

9J82J -> { 2, 1, 1, 1 }[0] + 2 jokers = 4 -> four of a kind

So, the key was to skip jokers in the set of multiples and the above example results in { 1, 1, 1 }[0] + 2 jokers = 3. Working. I spotted this error at this sample from my personal input, JKJ5J, which resulted in three of a kind and not the expected full house.

Lesson learned, always challenge you initial solution approach.

Day 8

Pretty easy first part, with deception in the second part. Ignoring the need for a simultaneous approach and using lowest common multiple calculation did the trick for me.

Disclaimer:
I used IntelliJs AI Assistant to provide me with a readymade LCM algorithm.

1
2
fun gcd(a: Long, b: Long): Long = if (b == 0L) a else gcd(b, a % b)
fun lcm(a: Long, b: Long): Long = a / gcd(a, b) * b

I personally do not consider this as using AI to solve the puzzle. LCM is a pretty common algorithm and not really the solution itself.

Day 9 to 13

Busy days, not proud of some of the solutions, maybe I’ll update, maybe not.

Day 14

Ok, started pretty calm. I opted for a basic object-oriented approach, implemented a grid and yes, moved the rocks step-by-step.

1
2
3
4
5
6
7
8
9
10
11
12
13
grid.forEach {
var movement = true
while (movement) {
movement = false
for (i in 0..it.size - 2) {
if (it[i] == '.' && it[i + 1] == 'O') {
it[i] = 'O'
it[i + 1] = '.'
movement = true
}
}
}
}

With this algorithm, the rocks can only be moved east to west, but by rotating the grid, every direction can be achieved. Little did I know that this was an important feature in part two.

And there comes the spin: quite simple, as I conveniently added a tilt method fun tilt(direction: Direction), accepting all possible directions NORTH, EAST, SOUTH and WEST:

1
2
3
4
5
6
fun spin() {
tilt(Direction.NORTH)
tilt(Direction.WEST)
tilt(Direction.SOUTH)
tilt(Direction.EAST)
}

But iterating 1000000000 times takes a lot of time, even with the example input. Coming up with cycle detection took me even longer, and the resulting implementation was just painful.
Anyway, the resulting code is pretty neat.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private fun spin(dish: Dish, cycles: Long) {
var i: Long = 0
var distance: Long = 0
var patterns: MutableMap<String, Long> = mutableMapOf()

while(i < cycles) {
if (patterns.containsKey(dish.toString())) {
distance = i - patterns[dish.toString()]!!
break
}
patterns[dish.toString()] = i
dish.spin()
i++
}

if (distance > 0) {
val remainingCycles = (cycles - i) % distance
for (i in 1..remainingCycles) {
dish.spin()
}
}
}

I wish I had come up with a more sophisticated, more mathematical solution, as this is basically just brute force. But it works and the code is still somewhat readable. Job done.

Day 15

Though the tasks were relatively easy, I really had a hard time understanding the actual task in part two of the challenge. In particular, determining the box number to add and remove lenses to. Therefore, I tried using the lens ids hash. Successfully, but I still cannot tell you exactly why.

And I don’t care. If it works, it works.

Resources