Skip to content

Commit d3caeac

Browse files
committed
Solve 2025 day 9 part 2
1 parent 2e607b7 commit d3caeac

File tree

2 files changed

+104
-9
lines changed

2 files changed

+104
-9
lines changed

src/main/scala/eu/sim642/adventofcode2025/Day9.scala

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,103 @@ package eu.sim642.adventofcode2025
22

33
import eu.sim642.adventofcodelib.box.Box
44
import eu.sim642.adventofcodelib.pos.Pos
5+
import eu.sim642.adventofcodelib.SeqImplicits.*
6+
import eu.sim642.adventofcodelib.graph.{BFS, GraphTraversal, UnitNeighbors}
7+
8+
import scala.collection.immutable.SortedSet
9+
import scala.collection.mutable
510

611
object Day9 {
712

8-
def largestArea(redTiles: Seq[Pos]): Long = {
9-
(for {
10-
// faster than combinations(2)
11-
(p1, i) <- redTiles.iterator.zipWithIndex
12-
p2 <- redTiles.view.slice(i + 1, redTiles.size).iterator
13-
} yield Box.bounding(Seq(p1, p2)).size[Long]).max
13+
trait Part {
14+
def largestArea(redTiles: Seq[Pos]): Long
15+
}
16+
17+
object Part1 extends Part {
18+
override def largestArea(redTiles: Seq[Pos]): Long = {
19+
(for {
20+
// faster than combinations(2)
21+
(p1, i) <- redTiles.iterator.zipWithIndex
22+
p2 <- redTiles.view.slice(i + 1, redTiles.size).iterator
23+
} yield Box.bounding(Seq(p1, p2)).size[Long]).max
24+
}
25+
}
26+
27+
object Part2 extends Part {
28+
override def largestArea(redTiles: Seq[Pos]): Long = {
29+
// TODO: clean up
30+
// TODO: optimize (with polygon checks?)
31+
val xs = redTiles.map(_.x).distinct.sorted
32+
val ys = redTiles.map(_.y).distinct.sorted
33+
//println(xs)
34+
//println(ys)
35+
36+
def mapPos(p: Pos): Pos =
37+
Pos(xs.indexOf(p.x) * 2 + 1, ys.indexOf(p.y) * 2 + 1) // leave edge around for fill
38+
39+
val grid = mutable.ArraySeq.fill(ys.size * 2 - 1 + 2, xs.size * 2 - 1 + 2)('.')
40+
41+
for ((redTile1, redTile2) <- redTiles lazyZip redTiles.rotateLeft(1)) {
42+
val gridPos1 = mapPos(redTile1)
43+
val gridPos2 = mapPos(redTile2)
44+
for (gridPos <- Box.bounding(Seq(gridPos1, gridPos2)).iterator)
45+
grid(gridPos.y)(gridPos.x) = 'X'
46+
}
47+
48+
for (redTile <- redTiles) {
49+
val gridPos = mapPos(redTile)
50+
//println((redTile, gridPos))
51+
grid(gridPos.y)(gridPos.x) = '#'
52+
}
53+
54+
//for (row <- grid) {
55+
// for (cell <- row)
56+
// print(cell)
57+
// println()
58+
//}
59+
60+
val graphTraversal = new GraphTraversal[Pos] with UnitNeighbors[Pos] {
61+
override val startNode: Pos = Pos.zero
62+
63+
override def unitNeighbors(pos: Pos): IterableOnce[Pos] =
64+
Pos.axisOffsets.map(pos + _).filter(p => p.x >= 0 && p.y >= 0 && p.y < grid.size && p.x < grid(p.y).size && grid(p.y)(p.x) == '.')
65+
}
66+
67+
val outside = BFS.traverse(graphTraversal).nodes
68+
69+
def isValid(box: Box): Boolean = {
70+
val gridBox = Box(mapPos(box.min), mapPos(box.max))
71+
!gridBox.iterator.exists(outside)
72+
}
73+
74+
(for {
75+
// faster than combinations(2)
76+
(p1, i) <- redTiles.iterator.zipWithIndex
77+
p2 <- redTiles.view.slice(i + 1, redTiles.size).iterator
78+
box = Box.bounding(Seq(p1, p2))
79+
if isValid(box)
80+
} yield box.size[Long]).max
81+
}
1482
}
1583

84+
/*object Part2 extends Part {
85+
override def largestArea(redTiles: Seq[Pos]): Long = {
86+
87+
def isValid(box: Box): Boolean = {
88+
val boxCorners = Seq(box.min, Pos(box.max.x, box.min.y), box.max, Pos(box.min.x, box.max.y))
89+
???
90+
}
91+
92+
(for {
93+
// faster than combinations(2)
94+
(p1, i) <- redTiles.iterator.zipWithIndex
95+
p2 <- redTiles.view.slice(i + 1, redTiles.size).iterator
96+
box = Box.bounding(Seq(p1, p2))
97+
if isValid(box)
98+
} yield box.size[Long]).max
99+
}
100+
}*/
101+
16102
def parseRedTile(s: String): Pos = s match {
17103
case s"$x,$y" => Pos(x.toInt, y.toInt)
18104
}
@@ -22,6 +108,7 @@ object Day9 {
22108
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day9.txt")).mkString.trim
23109

24110
def main(args: Array[String]): Unit = {
25-
println(largestArea(parseRedTiles(input)))
111+
println(Part1.largestArea(parseRedTiles(input)))
112+
println(Part2.largestArea(parseRedTiles(input)))
26113
}
27114
}

src/test/scala/eu/sim642/adventofcode2025/Day9Test.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@ class Day9Test extends AnyFunSuite {
1616
|7,3""".stripMargin
1717

1818
test("Part 1 examples") {
19-
assert(largestArea(parseRedTiles(exampleInput)) == 50)
19+
assert(Part1.largestArea(parseRedTiles(exampleInput)) == 50)
2020
}
2121

2222
test("Part 1 input answer") {
23-
assert(largestArea(parseRedTiles(input)) == 4729332959L)
23+
assert(Part1.largestArea(parseRedTiles(input)) == 4729332959L)
24+
}
25+
26+
test("Part 2 examples") {
27+
assert(Part2.largestArea(parseRedTiles(exampleInput)) == 24)
28+
}
29+
30+
ignore("Part 2 input answer") { // TODO: optimize (~8.5s)
31+
assert(Part2.largestArea(parseRedTiles(input)) == 1474477524L)
2432
}
2533
}

0 commit comments

Comments
 (0)