I decided to do Advent of Code 2019 Day 4! I’m gonna solve the puzzles in Scala! I’m starting late, because I found out about it late. So we’re going to start at Day 4, and catch up in another post, or posts.
I have also decided to implement the solutions in Scala. My Scala skills have gotten rusty, so this is a good time to exercise them again. I have forgotten much of my Scala, since I don’t use it. Reading another post, in Kotlin, showed me just how much I’ve forgotten of the standard library functions. My first solution follows. I may add another solution using the Scala Standard Library functions to do even less actual code.
The full code is on my gitlab.
Part 1
To start with, I have a simple executable that runs the valid password count method, and prints it out to the command line. I just hard-coded the range, rather than reading in the input from a file.
object Puzzle4Main extends App {
val p4 = new Puzzle4
val range = 134792 to 675810
println("Valid passwords Part 1: " + p4.validPasswordCount1(range))
println("Valid passwords Part 2: " + p4.validPasswordCount2(range))
}
I didn’t start with both parts, but that’s the final product. Next I took the criteria from the puzzle and built a scalatest FunSpec for part one.
class Puzzle4Spec extends AnyFunSpec {
val p4 = new Puzzle4
describe("Tests for puzzle 4: part 1") {
it("111111 meets the password criteria") {
assert(p4.passwordValidPart1("111111"))
}
it("223450 does not meet the criteria") {
assert(!p4.passwordValidPart1("223450"))
}
it("123789 does not meet criteria") {
assert(!p4.passwordValidPart1("123789"))
}
it("123455 meets criteria") {
assert(p4.passwordValidPart1("123455"))
}
}
}
I added another criteria when I wasn’t sure if my indexing logic was actually doing the right thing. These tests all passed my implementation, eventually.
def passwordValidPart1(input: String): Boolean = {
if (input.length != 6) {
false
} else {
val numbers = input.toCharArray.map(_.asDigit).toList
atLeastOneDouble(numbers) && numbersNeverDecrease(numbers)
}
}
def atLeastOneDouble(numbers: List[Int]): Boolean = {
(0 until (numbers.length - 1)).exists(index => numbers(index) == numbers(index + 1))
}
def numbersNeverDecrease(numbers: List[Int]): Boolean = {
(0 until (numbers.length - 1)).forall(index => numbers(index) <= numbers(index + 1))
}
Since I’ve forgotten lots of Scala, especially the methods available to me on the standard library, I probably did a lot manually here that I didn’t need to. It passes the tests, however, and produces an accurate result!
Part 2
This revealed part two of the puzzle. Tests first!
describe("Puzzle 4: part 2") {
it("112233 meets criteria") {
assert(p4.passwordValidPart2("112233"))
}
it("123444 does not meet") {
assert(!p4.passwordValidPart2("123444"))
}
it("111122 meets criteria") {
assert(p4.passwordValidPart2("111122"))
}
}
My implementation here is arguably more complex than the first one, and looking at the kotlin blog, I should’ve remembered a lot more of the standard library.
def validPasswordCount2(inclusive: Range.Inclusive): Int = {
inclusive.count(number => passwordValidPart2(number.toString))
}
def passwordValidPart2(input: String): Boolean = {
val numbers = input.toCharArray.map(_.asDigit).toList
val grouped = numberGroups(numbers)
numbersNeverDecrease(numbers) && numberGroupsOfOnlyTwo(grouped)
}
def numbersNeverDecrease(numbers: List[Int]): Boolean = {
(0 until (numbers.length - 1)).forall(index => numbers(index) <= numbers(index + 1))
}
// find the groups of numbers
def numberGroups(numbers: List[Int]): List[List[Int]] = {
@tailrec def findGroups(working: List[Int], acc: List[List[Int]], previousNumber: Int): List[List[Int]] = {
if (working.isEmpty) {
acc
} else {
val current = working.head
if (current == previousNumber) {
val sequence = acc.head :+ current
findGroups(working.tail, sequence :: acc.tail, current)
} else {
val sequence = List(current)
findGroups(working.tail, sequence :: acc, current)
}
}
}
findGroups(numbers, List.empty[List[Int]], -1).reverse
}
def numberGroupsOfOnlyTwo(groups: List[List[Int]]): Boolean = {
groups.exists(_.length == 2)
}
I reused the numbersNeverDecrease
method, and then built a couple of methods to group the numbers into a list of lists, and then make sure I had at least one group of numbers that was only a size of 2. I think I can use a Scala standard library method to do the same thing, but it is fun to write tail recursive methods 🙂
Further reading
- My gitlab project containing all of the solutions
- Full solution for day 4
- Advent of Code 2019!
- All my Advent of Code 2019 Posts