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'
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
- Index of All Solutions - All posts and solutions for 2020, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 2
- Advent of Code - Come join in and do these challenges yourself!