11package eu .sim642 .adventofcode2025
22
33import eu .sim642 .adventofcode2018 .Day11 .SumGrid
4- import eu .sim642 .adventofcodelib .box .Box
5- import eu .sim642 .adventofcodelib .pos .Pos
64import eu .sim642 .adventofcodelib .SeqImplicits .*
5+ import eu .sim642 .adventofcodelib .box .Box
76import eu .sim642 .adventofcodelib .graph .{BFS , GraphTraversal , UnitNeighbors }
7+ import eu .sim642 .adventofcodelib .pos .Pos
88
9- import scala .collection .immutable .SortedSet
109import scala .collection .mutable
1110
1211object Day9 {
@@ -36,25 +35,20 @@ object Day9 {
3635 class ArrayPartialSumGrid (f : Pos => Int , box : Box ) extends SumGrid {
3736 val Box (min, max) = box
3837 val Pos (width, height) = max - min + Pos (1 , 1 )
39- val partialSums : mutable.ArraySeq [mutable.ArraySeq [Int ]] = mutable.ArraySeq .fill(height, width)(0 )
40- // TODO: should be larger by 1 to allow min-coordinate queries?
41-
42- for (y <- 0 until height) {
43- for (x <- 0 until width) {
44- val pos = min + Pos (x, y)
45- val sum =
46- (if (y >= 1 ) partialSums(y - 1 )(x) else 0 ) +
47- (if (x >= 1 ) partialSums(y)(x - 1 ) else 0 ) -
48- (if (x >= 1 && y >= 1 ) partialSums(y - 1 )(x - 1 ) else 0 ) +
49- f(pos)
38+ val partialSums : mutable.ArraySeq [mutable.ArraySeq [Int ]] = mutable.ArraySeq .fill(height + 1 , width + 1 )(0 ) // larger by 1 to allow min-coordinate queries
39+
40+ for (y <- 1 until height + 1 ) {
41+ for (x <- 1 until width + 1 ) {
42+ val pos = min + Pos (x - 1 , y - 1 )
43+ val sum = partialSums(y - 1 )(x) + partialSums(y)(x - 1 ) - partialSums(y - 1 )(x - 1 ) + f(pos)
5044 partialSums(y)(x) = sum
5145 }
5246 }
5347
5448 override def sumBox (box : Box ): Int = {
5549 // val Box(topLeft, bottomRight) = box
56- val topLeft = box.min - min
57- val bottomRight = box.max - min
50+ val topLeft = box.min - min + Pos ( 1 , 1 )
51+ val bottomRight = box.max - min + Pos ( 1 , 1 )
5852 val bottomLeft1 = Pos (topLeft.x - 1 , bottomRight.y)
5953 val topRight1 = Pos (bottomRight.x, topLeft.y - 1 )
6054 val topLeft1 = Pos (topLeft.x - 1 , topLeft.y - 1 )
@@ -63,32 +57,29 @@ object Day9 {
6357 }
6458
6559 object Part2 extends Part {
66- override def makeIsValid (redTiles : Seq [Pos ]): Box => Boolean = {
67- // TODO: clean up
68- // TODO: optimize (with polygon checks?)
60+ def makeCompressPos (redTiles : Seq [Pos ]): Pos => Pos = {
6961 val xs = redTiles.map(_.x).distinct.sorted
7062 val ys = redTiles.map(_.y).distinct.sorted
71- // println(xs)
72- // println(ys)
7363
74- def mapPos ( p : Pos ) : Pos =
64+ p =>
7565 Pos (xs.indexOf(p.x) * 2 + 1 , ys.indexOf(p.y) * 2 + 1 ) // leave edge around for fill
66+ // it's much faster to do the +1-s here than add Pos(1, 1) below
67+ }
7668
77- val grid = mutable.ArraySeq .fill(ys.size * 2 - 1 + 2 , xs.size * 2 - 1 + 2 )('.' )
69+ override def makeIsValid (redTiles : Seq [Pos ]): Box => Boolean = {
70+ val compressPos = makeCompressPos(redTiles)
71+ def compressBox (box : Box ): Box =
72+ Box (compressPos(box.min), compressPos(box.max))
7873
79- for ((redTile1, redTile2) <- redTiles lazyZip redTiles.rotateLeft(1 )) {
80- val gridPos1 = mapPos(redTile1)
81- val gridPos2 = mapPos(redTile2)
82- for (gridPos <- Box .bounding(Seq (gridPos1, gridPos2)).iterator)
83- grid(gridPos.y)(gridPos.x) = 'X'
84- }
74+ val originalBox = Box .bounding(redTiles)
75+ val compressedBox @ Box (_, compressedMax) = compressBox(originalBox)
8576
86- for (redTile <- redTiles) {
87- val gridPos = mapPos(redTile)
88- // println((redTile, gridPos))
89- grid(gridPos.y)(gridPos.x) = '#'
77+ val grid = mutable.ArraySeq .fill(compressedMax.y + 2 , compressedMax.x + 2 )(false ) // leave edge around for fill
78+ // TODO: why need +2?
79+ for ((redTile1, redTile2) <- redTiles lazyZip redTiles.rotateLeft(1 )) {
80+ for (pos <- compressBox(Box .bounding(Seq (redTile1, redTile2))).iterator)
81+ grid(pos.y)(pos.x) = true
9082 }
91-
9283 // for (row <- grid) {
9384 // for (cell <- row)
9485 // print(cell)
@@ -99,40 +90,24 @@ object Day9 {
9990 override val startNode : Pos = Pos .zero
10091
10192 override def unitNeighbors (pos : Pos ): IterableOnce [Pos ] =
102- 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) == '.' )
93+ 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))
10394 }
104-
10595 val outside = BFS .traverse(graphTraversal).nodes
10696
107- val outsideSumGrid = new ArrayPartialSumGrid (pos => if (outside(pos)) 1 else 0 , Box (Pos .zero, Pos (xs.size * 2 , ys.size * 2 )))
108-
97+ val outsideSumGrid = new ArrayPartialSumGrid (pos => if (outside(pos)) 1 else 0 , compressedBox)
10998 def isValid (box : Box ): Boolean = {
110- val gridBox = Box (mapPos (box.min), mapPos (box.max))
111- // !gridBox .iterator.exists(outside)
112- outsideSumGrid.sumBox(gridBox ) == 0
99+ val compressedBox = Box (compressPos (box.min), compressPos (box.max))
100+ // !compressedBox .iterator.exists(outside)
101+ outsideSumGrid.sumBox(compressedBox ) == 0
113102 }
114103
115104 isValid
116105 }
117106 }
118107
119- /* object Part2 extends Part {
120- override def largestArea(redTiles: Seq[Pos]): Long = {
121-
122- def isValid(box: Box): Boolean = {
123- val boxCorners = Seq(box.min, Pos(box.max.x, box.min.y), box.max, Pos(box.min.x, box.max.y))
124- ???
125- }
126-
127- (for {
128- // faster than combinations(2)
129- (p1, i) <- redTiles.iterator.zipWithIndex
130- p2 <- redTiles.view.slice(i + 1, redTiles.size).iterator
131- box = Box.bounding(Seq(p1, p2))
132- if isValid(box)
133- } yield box.size[Long]).max
134- }
135- }*/
108+ // TODO: line of sight in all directions solution (single iteration over corners, not all pairs!)
109+ // TODO: glguy's solution
110+ // TODO: polygon-based solution?
136111
137112 def parseRedTile (s : String ): Pos = s match {
138113 case s " $x, $y" => Pos (x.toInt, y.toInt)
0 commit comments