Matt McKenna
Matt McKenna

Matt McKenna

Advent of Code 2021 in Kotlin!

Advent of Code 2021 in Kotlin!

Exploring the awesome language of Kotlin

Matt McKenna
Β·Dec 3, 2021Β·

7 min read

Play this article

Table of contents

  • Setup
  • Day 01
  • Day 02

The Advent of Code event started this week and I will be writing about the problems and my solutions! I will be using Kotlin to complete them and I suggest that you do as well!


🚨 I highly recommend giving the problem a shot yourself before reading here. They are fun and a great way to learn more about Kotlin! If you want some guidance without just looking at answers reach out on Twitter and I'll give you some hints.


Jetbrains has created a template repository for you to get started! It only takes a minute to set up and they have a nice tutorial.

I'll skip the narrative part of the problem, but I highly suggest checking it out on the Advent of Code site!

Let's get started!

Setup

Make sure you follow the Jetbrains tutorial and make a repository from the template linked above.

Day 01

Part 1

Given an input of a list of numbers, determine how many are larger than the previous measurement?

199 (N/A - no previous measurement)
200 (increased)
208 (increased)
210 (increased)
200 (decreased)
207 (increased)
240 (increased)
269 (increased)
260 (decreased)
263 (increased)

In this example, there are 7 measurements that are larger than the previous measurement.

So my first thought is we are going to need a way to accept this input. Thanks to the Jetbrains Template this is done for us. We just need to make a Day01.txt file and it will be read in using the provided extension functions.

Next lets think about the main ask of the problem. Check two numbers in the list and if the second number is larger than the first, increment a counter.

To this we need to first make sure we are dealing with Ints. We can do this by mapping the input.

map is applied to a Collection and will result in a new Collection with the type of the applied operation. In this case, toInt()

input.map { it.toInt() }

Now that we have all Int's lets think about how to check elements from the list against each other.

var result = 0
var previous = input.first()

input.forEach { num ->
  if (num > previous) {
    result ++
  }

  previous = num
}

There is a tricky part here. The first element doesn't count for our readings. To avoid an off by one error we set previous to be the first element. By doing this we can guarantee that the count isn't incremented for this case.

We can turn this into an extension function that returns the result! Using count allows us to remove the var result. Adding this to our mapped input we can get the solution for part 1!

private fun List<Int>.countSequentialIncreases(): Int {
  var previous = this.first() // Start with the first value so we are not off by 1

  return count { current ->
    (current > previous).also { previous = current }
  }
}

fun part1(input: List<String>): Int {
  return input
    .map { it.toInt() } // Make everything in the list an integer.
    .countSequentialIncreases()
}

Part 2

Part 2 adds some complexity. This problem is similar to the first but wants to sum sections of threes to see if there is an increase. For instance add all of A, all of B and if sum(B) > sum(A) increment a count, then continue to sum(B) > sum(C) etc.

199  A
200  A B
208  A B C
210    B C D
200  E   C D
207  E F   D
240  E F G
269    F G H
260      G H
263        H

Kotlin has a great solution for this as well! We can start the same way as Part 1 with reading the input and mapping to Int. This time we will add the .windowed() function! This function will do exactly what we need by creating a List<List<T>>!

listOf(1,2,3,4,5).windowed(3) will result in a list like this: [ [1, 2, 3], [2, 3, 4], [3, 4, 5] ]

fun part2(input: List<String>): Int {
  return input
    .map { it.toInt() }
    .windowed(
      size = 3, // Segments of size 3
      step = 1, // Move the window by 1
      partialWindows = false, // Partial windows are not allowed from the prompt.
    )
    .map { window -> window.sum() } // Map the List of windows to a List<Int> of the sums
    .countSequentialIncreases() // Count sequential increases again!
}

This was a fun problem that shows off how great Kotlin is at dealing with Collections.


Day 02

Part 1

Day 2 adds some complexity because now each line of our input is two values. Let's go!

Input Example:

- forward 5
- down 5
- forward 8
- up 3
- down 8
- forward 2

Calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth?

For this part there is a math trick we can use. Since we are only dealing with addition and subtraction, we can collect all of the values by their direction type. Then perform the math operations once!

First I defined our directions as String constants.

const val FORWARD = "forward"
const val DOWN = "down"
const val UP = "up"

Next I made a function called associateDirections to generate a map of direction to amount. The tricky part here is to make sure the value in the map doesn't get overridden for each put call. This is why I used associateByTo. It let me define a destination Map that I could reference in the valueTransform to keep adding to the value at that key.

private fun associateDirections(input: List<String>): Map<String, Int> {
  val result = mutableMapOf<String, Int>()

  input
    .map { it.split(" ") } // Map to a List<List<String>> each element is guaranteed 2 items
    .associateByTo(
      destination = result, // We need a result map to store the current values for reference
      keySelector = { pair -> pair[0] }, // The key will be the direction string
      valueTransform = { pair ->
        // The value is the current direction's amount plus all previous amounts for that direction
        // Default to 0 if the direction doesn't yet exist.
        pair[1].toInt() + result.getOrDefault(pair[0], 0)
      }
    )

  return result
}

Now I can use this in the Part 1 solution.

fun part1(input: List<String>): Int {
  val directionsMap = associateDirections(input)

  val forward = directionsMap.getOrDefault(FORWARD, 0)
  val up = directionsMap.getOrDefault(UP, 0)
  val down = directionsMap.getOrDefault(DOWN, 0)

  val depth = down - up // up moves us in the negative direction from the prompt

  return forward * depth
}

Part 2

Unfortunately the map trick from Part 1 will not work in Part 2. This is because Part 2 has some new instructions and a new variable to track, aim:

- down X increases your aim by X units.
- up X decreases your aim by X units.
- forward X does two things:
   - It increases your horizontal position by X units.
   - It increases your depth by your aim multiplied by X.

The most explicit way to handle this problem is with a forEach loop since each directional command is so specific in what it does. I decided to map this list into a Pair just because I could.

fun part2(input: List<String>): Int {
  // Start by defining all of the values we will need
  var forward = 0
  var aim = 0
  var depth = 0

  input
    .map { 
      val split = it.split(" ") // Split up the input into List<List<String>>
      Pair(split[0], split[1].toInt()) // Make them Pair<String, Int>
    }
    .forEach { pair ->
      // Perform the operations for each direction.
      when (pair.first) {
        FORWARD -> {
          forward += pair.second
          depth += pair.second * aim
        }
        DOWN -> {
          aim += pair.second
        }
        UP -> {
          aim -= pair.second
        }
      }
    }

  return forward * depth
}

Let me know if you have an questions in the comments or on Twitter.

Good luck with whatever Day you are working on and stay tuned for my write up on tomorrow's problem!

Β 
Share this