-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDay10.scala
75 lines (61 loc) · 2.65 KB
/
Day10.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.lmat.adventofcode.year2021
import com.lmat.adventofcode.SimpleCommonPuzzle
import com.lmat.util.Files.readResource
import scala.annotation.tailrec
object Day10 extends SimpleCommonPuzzle[List[String], Int, Long]{
override def parse(resource: String): List[String] =
readResource(resource).toList
override def part1(lines: List[String]): Int =
lines.flatMap(corrupted).map(scoreIllegal).sum
override def part2(lines: List[String]): Long = {
val scores = lines.filter(line => corrupted(line).isEmpty).map(autoComplete).map(scoreIncomplete)
middle(scores)
}
// Algorithm: We keep track of opened chunks in essentially a stack, removing each layer when finding a valid closing character
// On the first illegal character we terminate and return it
def corrupted(line: String): Option[Char] = {
@tailrec
def loop(opened: List[Char], remaining: List[Char]): Option[Char] =
remaining match {
case Nil => None
case ::(current, next) =>
if (isOpening(current)) loop(opened :+ current, next)
else if (isClosing(current) && opened.nonEmpty && matches(opened.last, current)) loop(opened.dropRight(1), next)
else Some(current)
}
loop(List(), line.toCharArray.toList)
}
// Invariant: The line is not corrupted
// Algorithm: We go through the line maintaining the stack of opened chunks
// Whatever remains at the end we have to close in reverse order
def autoComplete(line: String): String =
line.toCharArray
.foldLeft(List.empty[Char])((opened, current) => if (isOpening(current)) opened :+ current else opened.dropRight(1))
.reverse.map(close)
.mkString("")
// Invariant: The list has an odd number of scores
def middle[A: Ordering](scores: List[A]): A = {
val sorted: List[A] = scores.sorted
sorted(scores.size / 2)
}
def scoreIncomplete(completion: String): Long =
completion.toCharArray.foldLeft(0L)((score, current) => score * 5 + scoreIncomplete(current))
lazy val chunks = Map(
'(' -> ')',
'[' -> ']',
'{' -> '}',
'<' -> '>'
)
lazy val scores = Map(
')' -> (3, 1),
']' -> (57, 2),
'}' -> (1197, 3),
'>' -> (25137, 4)
)
def scoreIllegal(char: Char): Int = scores.get(char).map(_._1).getOrElse(0)
def scoreIncomplete(char: Char): Int = scores.get(char).map(_._2).getOrElse(0)
def isOpening(char: Char): Boolean = chunks.keySet.contains(char)
def isClosing(char: Char): Boolean = chunks.values.toSet.contains(char)
def matches(opening: Char, closing: Char): Boolean = chunks.toSet.contains((opening, closing))
def close(char: Char): Char = chunks.getOrElse(char, ' ')
}