Skip to main content

The power of collection pipelines 💪

This year, I’m participating in Advent of Code for the first time. If you don’t know it, it is a series of programming puzzles you can use to challenge yourself and/or compete with others.

As I finished the first day’s puzzles, I was struck by the power of collection pipelines. By collection pipelines I mean combining higher-order collection functions such as map and filter to perform a computation. Let me show you an example.

Example: Day 1, part 2 #

The example below is my solution to the second puzzle of Day 1. In particular, I thought it was a good example of how powerful collection pipelines can be. The examples in Kotlin, as that is what I use for Advent of Code.1

The goal of this particular task was:

Calculate a total similarity score by adding up each number in the left list after multiplying it by the number of times that number appears in the right list.

You are given a text file with two columns of numbers. The output should be a single number which you enter into the Advent of Code site. Without diving deeper into explaining the problem, this is my solution (lightly edited for the blog).

val lines = readInputFile(day = 1).readLines()
val (leftList, rightList) = lines.parseIntInt().unzip()
val countByValue = rightList.groupBy { it }.mapValues { it.value.size }
val result = leftList.sumOf { it * countByValue.getOrDefault(it, 0)}
println(result)

Everything in this code example uses the Kotlin standard library, except the following two helper functions I created for the purpose. I did not have these functions in advance, but extracted them afterwards as I expect them to be useful for future puzzles.

fun readInputFile(day: Int) = File("./src/main/kotlin/$day/input.txt")  
  
fun Iterable<String>.parseIntInt() = this.map { line ->  
	val (left, right) = line.split(" ", limit = 2)  
	left.trim().toInt() to right.trim().toInt()  
}

Keep in mind that this code was written as a one-off to solve a problem quickly. Making it readable or maintainable was not my first priority. With that said, I honestly think this code is pretty good and wouldn’t change too much if it would be used as production code.

What do you think?

The functions I used #

As a quick summary, I made use of the following collection-related functions.

  • map: Applies the given function to each element in the original list, producing a new list from the results.
  • unzip: Converts a list of pairs to a pair of lists, for example [(a, 1), (b, 2)] to ([a, b], [1, 2]).
  • groupBy: Returns a map with elements grouped by the value the given key selector function.
  • mapValues: A version of map that works on the Map data structure, changing the values but not the keys.
  • sumOf: Returns the sum of all values produced by the given function applied to each element in the list.
  • getOrDefault: Returns the value for the key, or a default value if no such key exists.

In addition to this, I also used readLines and split for reading and parsing the file.


  1. The Kotlin site has a page on Kotlin for competitive programming which I also things summarizes why I like Kotlin pretty well: “While not being specifically designed for competitive programming, Kotlin incidentally fits well in this domain, reducing the typical amount of boilerplate that a programmer needs to write and read while working with the code almost to the level offered by dynamically-typed scripting languages, while having tooling and performance of a statically-typed language.” ↩︎