« Index »

Things I learned about Kotlin from Advent of Code

(December 2021)

This year, I’m participating in the Advent of Code, a wonderful event that started in 2015 and has grown exponentially since. Little wonder because it’s a great place to find challenging and fun ways to practice and learn. AoC happens every December during Advent (duh!) from December 1st to the 25th. Just head on over to their website to learn more about it.

It has been a while since I first learned Kotlin and I haven’t done anything much in it recently so this was a perfect way to brush up on it. In retrospect, that was a great decision. I’m having a lot of fun re-learning Kotlin and appreciating just how enjoyable it is to program in it. I’m also discussing the solutions with my friends at CodeRanch in the Programming Diversions forum.

Lesson #1 - Kotlin is terse but extremely expressive

There’s usually a better and shorter way to say it in Kotlin. No, even better and shorter.

Case in point: Day 01 of AoC had us solving a relatively simple counting problem. Given a series of numbers, count how many times the series increases from one number to the next. For example, the sequence 0,1,2,1,3,4,4,5 increases 5 times: 0-1, 1-2, 1-3, 3-4, and 4-5.

My first solution worked and earned me my first gold star but it was brute force and ugly:

fun timesIncreasedBrute(nums: List<Int>): Int {
    var last = 0
    var count = 0
    nums.forEach {
        if (it > last) {
            count += 1
        }
        last = it
    }
    return count - 1
}

Of course, that could be written in a much shorter and clearer way.

Solving it with windowed()

The Kotlin windowed() function allows you to take snapshots of a collection with a sliding window. You just specify the window size and Kotlin will do the heavy lifting of sliding that window over the collection and return a list of snapshots, where each snapshot is itself a list of elements in the collection that fell within the sliding window. By default, the window will slide one element at a time but you can specify a different step size. For this problem, the default step size of 1 was exactly what I needed.

In Kotlin, the code using windowed() is straightforward and clear:

fun timesIncreased(values: List<Int>): Int = values
    .windowed(size = 2).count { it.first() < it.last() }

The function after count is a predicate that acts as a filter for the items that will be counted. Only the elements that satisfy the condition will be counted.

Neat, huh? Nine lines of brute force code boiled down to one line of exquisitely elegant expressiveness.

No, even shorter (and better maybe?)

The function expression that comes after count can be written with explicit parameters, too:

fun timesIncreased(values: List<Int>): Int = values
    .windowed(size = 2).count { a, b -> a < b }

This is definitely shorter than the previous version. I’ll let you decide if this is better or not; maybe it’s a matter of preference at this point. I like this version, too, but for some reason, I stuck with it.

Lesson #2 - You can .also { print(it) } to debug

See what I did there? ;)

In Java, a quick-and-dirty way to debug code is with System.out.println() statements. This can create more clutter in your code. Also, you often end up doing ugly things just to see a value, like creating lots of temporary variables. And it’s not always easy to peek at values while in the middle of evaluating an expression. For that, you have to fire up the debugger and start stepping through the code. If at all possible, I try to avoid doing that.

Of course, Kotlin has a very handy and elegant way to do that and keep the messiness under control. It also gives you a way to get into nooks and crannies that you wouldn’t normally be able to access in Java. The best thing about it is that I don’t have to go anywhere near the debugger.

They’re called scoped functions and the one that’s really useful for debugging, literally in the moment, is the also scoped function. This little beauty lets you peek at just about anything that’s happening anywhere, even in the middle of an expression.

Here’s an example of how to get a window (pun intended) into what was happening in the code we had earlier.

fun timesIncreased(values: List<Int>): Int = values
    .windowed(size = 2)
        .also { println(it) } // inline debug statement!
    .count { it.first() < it.last() }
        .also { println(it) } // inline debug statement!

timesIncreased(listOf(0, 1, 2, 1, 3, 4, 4, 5))

Here’s what the output looks like:

[[0, 1], [1, 2], [2, 1], [1, 3], [3, 4], [4, 4], [4, 5]]
5

Notice how I indented the alsos and put them on separate lines. This makes it easy to comment them out or delete them later when I’m done debugging.

The first line of the output gives you an idea of what windowed() does. In this case, it creates a list of pairs of consecutive elements, because I specified size=2. The second line displays the value returned by the count function.

The nice thing about the also scoped function is that it doesn’t interfere with the expression it’s added to at all: it’s basically a passthrough operation. It’s a great way to spy on expressions as they are being evaluated.

Use string interpolation with println() for cleaner, clearer messages

Those debug statements can be made even clearer and you can do it much more cleanly than you could with Java. Just use Kotlin’s handy dandy string interpolation.

fun timesIncreased(values: List<Int>): Int = values
    .windowed(size = 2)
        .also { println("windowed(2): $it") } // inline debug statement!
    .count { it.first() < it.last() }
        .also { println("timesIncreased() == $it") } // inline debug statement!

timesIncreased(listOf(0, 1, 2, 1, 3, 4, 4, 5))

Here’s what the output looks like now:

windowed(2): [[0, 1], [1, 2], [2, 1], [1, 3], [3, 4], [4, 4], [4, 5]]
timesIncreased() == 5

Pretty useful, right? You could also say it’s pretty and it’s useful. (I’m a dad so naturally, I think I’m so punny)

Lesson #3 - Use the built-in check() function for quick testing

Another thing I learned about was the built-in check() function, which takes a boolean expression and throws an IllegalStateException if the argument evaluates to false. This is kind of a poor man’s assert which I know the Kotlin standard library also provides.

The jury is still out on this one for me but it came out of the box with the
Advent Of Code Kotlin Template on GitHub so I just rolled with it.

In the template, check() was used to see if your solution matched what the problem’s example said to expect. With a few minor tweaks I made, this is essentially what you got with the template:

 // test if implementation meets criteria from the description, like:
 val testInput = readInput("DayXX_test")
 check(part1(testInput) == 1)

 val input = readInput("DayXX")
 println(part1(input))
 println(part2(input))

You’d change the number in the condition to match the problem’s example. Presumably, the idea is that you’d first develop a solution and try it out with the example data. Then if it matches, you could go ahead and solve the problem using the actual data set, which is much larger than the example data.

Using check with println and also was convenient both while developing the solution and when refactoring the code afterwards. Once I got my solution verified and earned my gold star, I would do this:

  println(part1(testInput).also { check(it == 37) }) // from the example

  ...
  println(part1(input).also { check(it == 5608) }) // verified solution
  println(part2(input).also { check(it == 20299) }) // verified solution

This way, I thought, I would know right away if any refactoring I attempted on the already working solution had screwed something up and I could then revert to the last working version of the code.

Looks nice but wait, it doesn’t quite work as intended

I soon figured out, however, that even though that incantation looked nice, it didn’t quite work the way I wanted it to work. The problem was that if I did mess up my refactoring, check() threw an exception before println() had a chance to display the value. Essentially, what I wanted was this:

  • call part1(testInput)
  • then println the result
  • then check if the value is 37

The way to say that in Kotlin is:

  part1(testInput).also(::println).also { check(it == 37) }

A little wordier, yes, but it actually does what I intended.

Notice how .also(::println) is equivalent to .also { println(it) }. I won’t go into the ins and outs of why that is but if you’re curious, try looking here for answers.

More lessons to come

I’ll continue to update this article throughout Advent. Stay tuned and in the meantime, stay safe!

Next article »


About the author

Junilu Lacar likes to spend his time learning, practicing, and helping others learn and practice things that he believes help software development teams deliver value in a sustainable and humane way.