Monday, December 1, 2025

Advent of Code 2025, Day 1

It's that time once again, to tell stories about elves with code. That's right  Advent of Code has come around again. Some folks treat try to solve these challenges with as little code as possible, to learn a new language or leverage the particular strengths of a language. This year I've decide to use the strong types in Kotlin to implement the problem space as closely as possibles using an object heavy approach. Originally that was just going to be "object oriented APIs" with whatever ever I needed to under the covers, but I eventually went full OO with this. 

The problem for Day 1 was based on the dial of a safe. Given the dial starts at 50, if you a apply series of rotations, how many times does the dial stop at 0. For example, given this set of rotations
  
  L68 
  L30 
  R48 
  L5 
  R60 
  L55 
  L1 
  L99 
  R14 
  L82
  
the tumbler would stop on 0 a total of 3 times.

The first step was how to model the inputs.

data class Turn(
     val direction: Direction,
     val quantity: Int)

Nice, clean, immutable data class. These things really don't get enough love. But what is a Direction?
enum class Direction(val abbr: Char, val multiplier: Int) {
    Left('L', -1),
    Right('R', 1);

    companion object {

        private val mapping = entries.associateBy { it.abbr }

        fun lookup(abbr: Char) : Direction{
            return mapping[abbr]?:throw IllegalArgumentException()
        }
    }
}

Maybe that lookup function should be in another class, a DirectionResolver or something like that. I'm focusing on the "modeling" part of OO, not so much the single responsibility principle. This small block of code shows a few neat tricks that Kotlin can provide because it's a DSL on top of Java.

A quick aside: 

You young folk out there don't remember this, but back at the turn of the century, Java didn't have enumerations.  Until Java 5 was released in 2005, you either had to use named int values (I'm looking you java.util.Calendar.December or had to use the boiler plate heavy pattern that Joshua Bloch described in Effective Java.  This frequently got of some "why even bother" kind of looks in the day, but like it says on the tin, it was effective.   I've not picked up a revised edition of the book, but the first edition was transformative for me.

To go from a "R14" to Turn(Direction.Right, 14), I created a class to do the mapping. Ok, I'll let you in on secret. I do love me some single responsibility principle.
class LineMapper() {

    fun map(line: String) : Turn {

        val d: Char = line[0]
        val q = line.substring(1)

        return Turn(
            Direction.lookup(d),
            q.toInt()
        )

    }

}

Processing the data files into a collection of turns is as simple as
    val lines = data.lines()
    val turns = lines.map { lineMapper.map(it) }
    
And you know I had to have a "dial" class.
class Dial() {

	private var currentState: Int =50

    fun turn(turn: Turn) {
       // a bunch of math and conditionals and absolute values
    }
    
    fun getCurrentState() : Int {
        return currentState
    }
    
}

A few design decisions here. 

1) in my original implementation this was data class, just out of habit. But given that the initial value of currentState attribute would never be provided by another class, I removed that keyword. 

 2) Kotlin attributes are public by default, but I made currentState private to secure access to the value. It can only be modified via the turn function.

Like I said above, the initial implementation was on OO in the APIs, as I've demonstrated so far.  And that worked for the first part of the challenge: how many times did the dial stop on 0.

The second part of the challenge was "how many times did the dial SHOW 0".  Now, I could do more math and conditionals and absolute values and track the before and after values from a turn and see if crossed 0.  Instead the code increments or decrements the integer value for ever tick the dial is turned. Which looked like this:

val tick = 1 * turn.direction.multiplier

(1..turn.quantity).forEach { _ ->
    currentState += tick
    if (currentState < 0) {
        currentState = 99
    } else if (currentState > 99) {
        currentState = 0
    }
    if (currentState == 0) {
        passesZero++
    }
}

So, there's just a simple increment operation, the bounds checking and a counter.
This is not going to win any awards for computational efficiency, that's for sure. But one of the strengths of Kotlin is the readability of the code, compared Java and especially to other lower level languages and I think this solution demonstrates that capability.
Ho, ho, ho an 'nat!

No comments:

Post a Comment