Skip to Content

Advent of Code 2020 - Day 2, in Kotlin - Password Philosophy

Kotlin solutions to parts 1 and 2 of Advent of Code 2020, Day 2: 'Password Philosophy'

Posted on

Welcome to Day 2 of Advent of Code 2020 in Kotlin! Today’s puzzle is mostly about parsing the input, which is a nice topic to cover early in the month before things get more complicated.

If you’d rather just view code, the GitHub Repository is here .

A Note About Types

Before we dig in I want to address one stylistic choice I make when writing about these puzzles. Although Kotlin has fantastic support for type inference and allows us to leave the types off of many functions or properties, I consciously keep them in. Since I don’t know the skill level of people reading this blog, I’d rather leave them in as a form of documentation. For the most part, this does not impact the underlying code and how it works. It is for clarity only. If you don’t like seeing the types, feel free to leave them off. I’m including them for documentation to make things easier for people to follow (especially since this is in text and not an IDE where you can inspect the types easily).

Problem Input

Our input is one String per row in a file. First, let’s create a Kotlin data class to hold this information.

data class PasswordRow(val range: IntRange, val letter: Char, val password: String)

We’ll add more to this class later on, but I want to point out that instead of having properties for min and max, we’re going to define those as an IntRange called range. Kotlin has top-level supports for ranges, and this is a nice use case for that.

Next, we’ll have to parse our input and create instances of PasswordRow from it. To do this, we’ll use a Regular Expression (or a “RegEx” as they are known). If you have never used a Regular Expression, they can be powerful but confusing. Think of them like a pattern for describing text. In our case, we have a RegEx that captures each element we care about in a group. We’ll define this Regex, and a function to use it in the companion of PasswordRow.

// In PasswordRow

companion object {

    private val pattern = """^(\d+)-(\d+) (\w): (.+)$""".toRegex()

    fun of(input: String): PasswordRow {
        val (min, max, letter, password) = pattern.find(input)!!.destructured
        return PasswordRow(min.toInt() .. max.toInt(), letter.first(), password)
    }
}

The first thing to note is our RegEx (pattern). This will parse a line of input and group four outputs for us - min, max, letter, and password, all as Strings. To use this, we call find on the pattern, which returns an object with our results. Since we know for a fact that all of our input will be valid, I’m using the Hold My Beer operator (!!) . Generally, I try to avoid it but in this case we’re fine.

Once we have that intermediate representation of our data, we destructure it in to its component variables and use those to create a PasswordRow. We have to convert min and max into integers via toInt(), and convert those into an IntRange via .. - the range operator.

Now that we have all that, we can use our new PasswordRow.of() function to transform our input:


class Day02(input: List<String>) {

    private val data: List<PasswordRow> = input.map { PasswordRow.of(it) }

}

We’ve parsed our input into a useful format, so we’re ready to solve Part 1.

⭐ Day 2, Part 1

The puzzle text can be found here.

First, let’s add a property to PasswordRow to calculate if part 1 is valid.

// In PasswordRow

val validPart1 = password.count { it == letter } in range

This expression takes advantage of the fact that we have a range instead of a min and a max. First, we use the count function from the Kotlin standard library to count up the number of chacters in the password that match our letter. We then see if that number is in our range by using the in operator.

Call this function, and we’ve solved part 1!

// In Day02

fun solvePart1(): Int = data.count { it.validPart1 }

Onward!

⭐ Day 2, Part 2

The puzzle text can be found here.

Much like yesterday, Part 2 is a slight derivation of Part 1.

We can add a new validPart2 property to our PasswordRow data class:

// In PasswordRow

val validPart2 = (password[range.first-1] == letter) xor (password[range.last-1] == letter)

Remember, according to the instructions, the passwords are 1-indexed, whereas Kotlin is 0-indexed. That means we’ll have to subtract 1 from each of our range indexes. Keeping that in mind, we get both of the characters in question and see if they match the letter. In order to make sure that one and only one of them matches, we use the xor inline function from the Kotlin standard library. If you haven’t run into xor before it stands for “exclusive or”. Meaning: Of the left and right sides, only one can be true. If they are both false or both true, xor is also false.

Once we have that, we can write part 2:

// In Day02

fun solvePart2(): Int = data.count { it.validPart2 }

Problem solved! See you tomorrow!

Further Reading

  1. Index of All Solutions - All posts and solutions for 2020, in Kotlin.
  2. My Github repo - Solutions and tests for each day.
  3. Solution - Full code for day 2
  4. Advent of Code - Come join in and do these challenges yourself!