-
Notifications
You must be signed in to change notification settings - Fork 0
/
Advent15.scala
116 lines (94 loc) · 3.25 KB
/
Advent15.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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package jurisk.adventofcode.y2023
import jurisk.utils.CollectionOps.VectorOps
import jurisk.utils.FileInput._
import jurisk.utils.Parsing.StringOps
import scala.annotation.tailrec
object Advent15 {
private type Steps = List[Step]
private type Label = String
private type FocalLength = Int
def calculateHash(s: String): Int =
s.toList.foldLeft(0) { case (acc, ch) =>
((acc + ch.toInt) * 17) % 256
}
def parse(input: String): Steps =
input.split(",").map(Step.parse).toList
sealed trait Step {
def label: Label
def originalString: String
def applyTo(lenses: List[Lens]): List[Lens]
}
object Step {
final private case class Remove(label: String) extends Step {
def originalString: Label = s"$label-"
def applyTo(lenses: List[Lens]): List[Lens] = {
@tailrec
def helper(remainingLenses: List[Lens], acc: List[Lens]): List[Lens] =
remainingLenses match {
case head :: tail if head.label == label =>
acc.reverse ::: tail
case head :: tail =>
helper(tail, head :: acc)
case Nil =>
acc.reverse
}
helper(lenses, List.empty)
}
}
final private case class Replace(
label: Label,
focalLength: FocalLength,
) extends Step {
def originalString: Label = s"$label=$focalLength"
def applyTo(lenses: List[Lens]): List[Lens] = {
@tailrec
def helper(remainingLenses: List[Lens], acc: List[Lens]): List[Lens] =
remainingLenses match {
case head :: tail if head.label == label =>
acc.reverse ::: Lens(label, focalLength) :: tail
case head :: tail =>
helper(tail, head :: acc)
case Nil =>
acc.reverse ::: Lens(label, focalLength) :: Nil
}
helper(lenses, List.empty)
}
}
def parse(s: String): Step =
s match {
case s"$label-" => Remove(label)
case s"$label=$focalLength" => Replace(label, focalLength.toInt)
case _ => s.failedToParse
}
}
final case class Lens(label: String, focalLength: Int)
final case class LensBox(lenses: List[Lens]) {
def value: Int =
lenses.zipWithIndex.map { case (lens, index) =>
(index + 1) * lens.focalLength
}.sum
}
private object LensBox {
def empty: LensBox = LensBox(List.empty)
}
def part1(data: Steps): Int =
data.map(op => calculateHash(op.originalString)).sum
def part2(ops: Steps): Int = {
val LensCount = 256
val boxen = ops.foldLeft(Vector.fill(LensCount)(LensBox.empty)) {
case (acc, op) =>
val idx = calculateHash(op.label)
acc.updatedWith(idx)(lensBox => LensBox(op.applyTo(lensBox.lenses)))
}
boxen.zipWithIndex.map { case (contents, idx) =>
(idx + 1) * contents.value
}.sum
}
def parseFile(fileName: String): Steps =
parse(readFileText(fileName))
def main(args: Array[String]): Unit = {
val realData: Steps = parseFile("2023/15.txt")
println(s"Part 1: ${part1(realData)}")
println(s"Part 2: ${part2(realData)}")
}
}