60
loading...
This website collects cookies to deliver better user experience
1-3 a: abcde
1-3 b: cdefg
2-9 c: ccccccccc
1-3 a
means that the password must contain a
at least once and at most 3 times. In the example, two passwords, the first and the third ones, are valid. The first contains one a
, and the third contains nine c
s, both within the limits of their respective policies. The second password, cdefg
, is invalid, as it contains no instances of b
but needs at least 1.1-3 a: abcde
is valid: position 1 contains a
and position 3 does not.1-3 b: cdefg
is invalid: neither position 1 nor position 3 contains b
.2-9 c: ccccccccc
is invalid: both positions 2 and position 9 contain c
.data
class for storing a given password together with its policy:data class PasswordWithPolicy(
val password: String,
val range: IntRange,
val letter: Char
)
IntRange
and Char
types, accordingly. The modifier data
instructs the compiler to generate some useful methods for this class, including the constructor, equals
, hashCode
, and toString
.PasswordWithPolicy.parse()
.data class PasswordWithPolicy(...) {
companion object {
fun parse(line: String): PasswordWithPolicy {
TODO()
}
}
}
TODO()
call that throws the NotImplementedError
exception. Because it returns the Nothing
type, which is a subtype of any other type, the compiler won’t complain. The function is supposed to return PasswordWithPolicy
, but throwing an exception inside is totally valid from the compiler’s point of view.String
s, including substringAfter()
and substringBefore()
which perfectly solve the task in our case:// 1-3 a: abcde
fun parse(line: String) = PasswordWithPolicy(
password = line.substringAfter(": "),
letter = line.substringAfter(" ").substringBefore(":").single(),
range = line.substringBefore(" ").let {
val (start, end) = it.split("-")
start.toInt()..end.toInt()
},
)
PasswordWithPolicy
constructor. The input format convention requires that: password
be the string that goes right after the colonstart
and end
), and build a range using the “..” operator converting both lines to Int
s. We use let
to convert the result of substringBefore()
to the desired range.substringAfter()
and similar functions take a second argument default to the string itself that should be returned in the event the given delimiter is not found. toInt()
and single()
have toIntOrNull()
and singleOrNull()
counterparts returning null
if the string can’t be converted to an integer number or consists of more than one character.(\d+)-(\d+) ([a-z]): ([a-z]+)
parse
function matches the line against a regular expression and then builds PasswordWithPolicy
on the result:private val regex = Regex("""(\d+)-(\d+) ([a-z]): ([a-z]+)""")
fun parseUsingRegex(line: String): PasswordWithPolicy =
regex.matchEntire(line)!!
.destructured
.let { (start, end, letter, password) ->
PasswordWithPolicy(password, start.toInt()..end.toInt(), letter.single())
}
Regex
class to define a regular expression. Note how we put it inside a triple-quoted string, so you don’t need to escape “\
”. For regular strings, escaping is done in the conventional way, with a backslash, so if you need a backslash character in the string, you repeat it: "(\\d+)-(\\d+)".
!!
” to throw an NPE if the input doesn’t correspond to the regular expression. An alternative is to use the safe access “?.
” here and return null
as the result.destructured
property provides components for a destructuring assignment for groups defined in the regular expression. We use its result together with let
and destruct it inside the lambda expression, defining start
, end
, letter
, and password
as parameters. As before, we need to convert strings to Int
s and Char
.data class PasswordWithPolicy(
val password: String,
val range: IntRange,
val letter: Char
) {
fun validatePartOne() =
password.count { it == letter } in range
...
}
letter
in the password
string, and check that the result is in range
. in
checks that the given element belongs to a range. For numbers and other comparable elements, x in range
is the same as writing explicitly range.first <= x && x <= range.last
.xor
operator for that, which returns true
if the operands are different. That’s exactly what we need here:fun validatePartTwo() =
(password[range.first - 1] == letter) xor (password[range.last - 1] == letter)
first
and last
because they imply indexing from one, but string indexation starts from zero.main
function that finds the result for both parts:fun main() {
val passwords = File("src/day2/input.txt")
.readLines()
.map(PasswordWithPolicy::parse)
println(passwords.count { it.validatePartOne() })
println(passwords.count { it.validatePartTwo() })
}
PasswordWithPolicy::parse
to the map
function to convert the strings to passwords.let
helps transform the expression nicely to the form you need.