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 L82the 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)
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