-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAdvent16.scala
120 lines (103 loc) · 3.53 KB
/
Advent16.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
117
118
119
120
package jurisk.adventofcode.y2024
import cats.implicits.catsSyntaxOptionId
import cats.implicits.none
import jurisk.algorithms.pathfinding.Bfs
import jurisk.algorithms.pathfinding.Dijkstra
import jurisk.collections.immutable.ImmutableBitSet
import jurisk.geometry.Coords2D
import jurisk.geometry.Direction2D
import jurisk.geometry.Direction2D.CardinalDirection2D
import jurisk.geometry.Field2D
import jurisk.geometry.Field2D.coordsToInt
import jurisk.geometry.Rotation
import jurisk.utils.CollectionOps.OptionOps
import jurisk.utils.FileInput._
import jurisk.utils.Parsing.StringOps
import jurisk.utils.ToInt
object Advent16 {
type Input = (State, Field2D[Boolean], Coords2D)
private type Cost = Int
final case class State(position: Coords2D, direction: CardinalDirection2D) {
def successors(field: Field2D[Boolean]): List[(State, Cost)] = {
val next = position + direction
val straight = field.at(next) match {
case Some(false) => State(next, direction).some
case _ => none
}
val rotateL = copy(direction = direction.rotate(Rotation.Left90))
val rotateR = copy(direction = direction.rotate(Rotation.Right90))
(rotateL, 1000) :: (rotateR, 1000) :: straight.map((_, 1)).toList
}
}
def parse(input: String): Input = {
val charField = Field2D.parseCharField(input)
val start =
charField.findCoordsByValue('S').getOrElse("Start not found".fail)
val end = charField.findCoordsByValue('E').getOrElse("End not found".fail)
val field = charField.mapByCoordsWithValues { case (_, c) =>
c match {
case 'S' => false
case 'E' => false
case '#' => true
case '.' => false
case _ => s"Unknown character $c in field".fail
}
}
(State(start, Direction2D.E), field, end)
}
def part1(data: Input): Cost = {
val (state, field, end) = data
val (_, result) = Dijkstra
.dijkstra[State, Int](
state,
state => state.successors(field),
_.position == end,
)
.orFail("Path not found")
result
}
def part2(data: Input): Cost = {
val best = part1(data)
println(s"Best: $best")
val (state, field, end) = data
implicit val c2i: ToInt[Coords2D] = coordsToInt(field)
var bestCostSoFar = Map.empty[State, Cost]
var visitedCoords = ImmutableBitSet.empty[Coords2D]
// Note: Actually, `bfsVisitAll` keeps another "visited" set, which is a duplicate
Bfs.bfsVisitAll[(State, Cost, ImmutableBitSet[Coords2D])](
(state, 0, ImmutableBitSet.empty),
{ case (state, cost, path) =>
if (cost < best) {
if (cost > bestCostSoFar.getOrElse(state, Int.MaxValue)) {
Nil
} else {
bestCostSoFar += (state -> cost)
state
.successors(field)
.map { case (n, c) =>
(n, cost + c, path + n.position)
}
}
} else {
Nil
}
},
{ case (state, cost, path) =>
if ((state.position == end) && (cost == best)) {
visitedCoords = visitedCoords ++ path
println(s"End state found: ${visitedCoords.size}")
}
},
)
visitedCoords.size
}
def parseFile(fileName: String): Input =
parse(readFileText(fileName))
def fileName(suffix: String): String =
s"2024/16$suffix.txt"
def main(args: Array[String]): Unit = {
val realData: Input = parseFile(fileName(""))
println(s"Part 1: ${part1(realData)}")
println(s"Part 2: ${part2(realData)}")
}
}