Part 2 working
This commit is contained in:
parent
327aaf4fd7
commit
ef76446306
1 changed files with 101 additions and 82 deletions
189
src/day09.scala
189
src/day09.scala
|
|
@ -12,98 +12,117 @@ val dayNumber = "09"
|
||||||
println(part2(loadInput(dayNumber)))
|
println(part2(loadInput(dayNumber)))
|
||||||
|
|
||||||
def part1(input: String): Long =
|
def part1(input: String): Long =
|
||||||
val tiles = input.split('\n').map { case s"$x,$y" => (x = x.toLong, y = y.toLong) }
|
input
|
||||||
tiles
|
.split('\n').map { case s"$x,$y" => (x = x.toLong, y = y.toLong) }
|
||||||
.combinations(2)
|
.combinations(2)
|
||||||
.collect {
|
.collect { case Array(a, b) => ((a.x - b.x + 1) * (a.y - b.y + 1)).abs }
|
||||||
case Array(a, b) if a != b =>
|
.max
|
||||||
((a.x - b.x + 1) * (a.y - b.y + 1)).abs
|
|
||||||
}.max
|
|
||||||
|
|
||||||
enum Edge:
|
|
||||||
case H(fixedY: Int, range: Range)
|
|
||||||
case V(fixedX: Int, range: Range)
|
|
||||||
|
|
||||||
def contains(x: Int, y: Int): Boolean =
|
|
||||||
this match
|
|
||||||
case H(fixedY, range) => fixedY == y && range.contains(x)
|
|
||||||
case V(fixedX, range) => fixedX == x && range.contains(y)
|
|
||||||
|
|
||||||
object Edge:
|
|
||||||
def apply(a: Point, b: Point): Edge =
|
|
||||||
if a.y == b.y then Edge.H(a.y, min(a.x, b.x) to max(a.x, b.x))
|
|
||||||
else if a.x == b.x then Edge.V(a.x, min(a.y, b.y) to max(a.y, b.y))
|
|
||||||
else ???
|
|
||||||
|
|
||||||
case class Polygon(edges: List[Edge]):
|
|
||||||
val minX = edges.map {
|
|
||||||
case Edge.H(_, range) => range.min
|
|
||||||
case Edge.V(fixedX, _) => fixedX
|
|
||||||
}.min - 1
|
|
||||||
|
|
||||||
def contains(p: Point) =
|
|
||||||
if edges.exists(_.contains(p.x, p.y)) then true
|
|
||||||
else
|
|
||||||
// println(p)
|
|
||||||
val (count, _) = (minX to p.x).foldLeft((0, false)) { case ((count, switched), x) =>
|
|
||||||
// println(x)
|
|
||||||
// print("edge contains ")
|
|
||||||
// println(edges.exists(_.contains(x, p.y)))
|
|
||||||
// print("switched ")
|
|
||||||
// println(switched)
|
|
||||||
if edges.exists(_.contains(x, p.y)) && !switched then (count + 1, true)
|
|
||||||
else (count, false)
|
|
||||||
}
|
|
||||||
// println("count")
|
|
||||||
// println(count)
|
|
||||||
count % 2 == 1
|
|
||||||
|
|
||||||
case class Point(x: Int, y: Int)
|
|
||||||
|
|
||||||
def part2(input: String): Long =
|
def part2(input: String): Long =
|
||||||
|
|
||||||
|
case class Point(x: Int, y: Int)
|
||||||
|
|
||||||
|
enum Edge:
|
||||||
|
case H(y: Int, xStart: Int, xEnd: Int)
|
||||||
|
case V(x: Int, yStart: Int, yEnd: Int)
|
||||||
|
|
||||||
|
object Edge:
|
||||||
|
def apply(a: Point, b: Point): Edge =
|
||||||
|
if a.y == b.y then
|
||||||
|
val xMin = min(a.x, b.x)
|
||||||
|
val xMax = max(a.x, b.x)
|
||||||
|
Edge.H(a.y, xMin, xMax)
|
||||||
|
else if a.x == b.x then
|
||||||
|
val yMin = min(a.y, b.y)
|
||||||
|
val yMax = max(a.y, b.y)
|
||||||
|
Edge.V(a.x, yMin, yMax)
|
||||||
|
else throw new IllegalArgumentException("Edges must be axis-aligned")
|
||||||
|
|
||||||
|
// Build the polygon edges out of the ordered red points.
|
||||||
val points = input.split('\n').map { case s"$x,$y" => Point(x.toInt, y.toInt) }
|
val points = input.split('\n').map { case s"$x,$y" => Point(x.toInt, y.toInt) }
|
||||||
val polygon = Polygon(
|
val edges = (points :+ points.head).sliding(2).map { case Array(a, b) => Edge(a, b) }.toList
|
||||||
(points :+ points.head).sliding(2).map { case Array(a, b) => Edge(a, b) }.toList
|
val verticalEdges = edges.collect { case v: Edge.V => v }
|
||||||
)
|
|
||||||
|
|
||||||
def corners(a: Point, b: Point) =
|
// Coordinate compression: keep only x or y values where something changes
|
||||||
List(
|
// (a boundary or its immediately-adjacent value). Each consecutive pair in `xs`/`ys`
|
||||||
Point(min(a.x, b.x), min(a.y, b.y)),
|
// represents one strip of actual tiles in the original grid.
|
||||||
Point(min(a.x, b.x), max(a.y, b.y)),
|
val xs = (points.map(_.x) ++ points.map(_.x + 1)).distinct.sorted
|
||||||
Point(max(a.x, b.x), min(a.y, b.y)),
|
val ys = (points.map(_.y) ++ points.map(_.y + 1)).distinct.sorted
|
||||||
Point(max(a.x, b.x), max(a.y, b.y))
|
|
||||||
).distinct
|
|
||||||
|
|
||||||
// println(polygon.contains(Point(9, 3)))
|
val xIndex = xs.zipWithIndex.toMap
|
||||||
|
val yIndex = ys.zipWithIndex.toMap
|
||||||
|
|
||||||
// println(polygon.edges.mkString("\n"))
|
// allowed(i)(j) tells us whether the compressed cell spanning
|
||||||
|
// x in [xs(i), xs(i+1)) and y in [ys(j), ys(j+1)) belongs to the red/green region.
|
||||||
|
val allowed = Array.ofDim[Boolean](xs.length - 1, ys.length - 1)
|
||||||
|
|
||||||
// println(polygon.edges.exists(_.contains(9, 3)))
|
// Even–odd scanline: for each horizontal strip, walk the vertical edges to mark the
|
||||||
// println(polygon.edges.exists(_.contains(9, 5)))
|
// interior spans between every pair of crossings.
|
||||||
// println(polygon.contains(Point(11, 1)))
|
for j <- 0 until ys.length - 1 do
|
||||||
// println(polygon.contains(Point(11, 7)))
|
val ySample = ys(j)
|
||||||
// println(polygon.contains(Point(11, 8)))
|
val crossings = verticalEdges.collect {
|
||||||
// println(polygon.contains(Point(11, 9)))
|
case Edge.V(x, yStart, yEnd) if ySample >= yStart && ySample < yEnd => x
|
||||||
|
}.sorted
|
||||||
// corners(Point(9, 5), Point(2, 3)).foreach(p =>
|
crossings.grouped(2).foreach {
|
||||||
// println(p)
|
case List(left, right) =>
|
||||||
// println(polygon.contains(p))
|
val start = xIndex(left) + 1
|
||||||
// )
|
val end = xIndex(right)
|
||||||
|
if start < end then for i <- start until end do allowed(i)(j) = true
|
||||||
println(
|
case _ =>
|
||||||
points
|
}
|
||||||
.combinations(2)
|
|
||||||
.map { case Array(a, b) =>
|
// Include the boundary itself so rectangles that ride along the green loop remain valid.
|
||||||
corners(a, b)
|
edges.foreach {
|
||||||
}.distinct.size
|
case Edge.H(y, xStart, xEnd) =>
|
||||||
)
|
val xiStart = xIndex(xStart)
|
||||||
points
|
val xiEnd = xIndex(xEnd + 1)
|
||||||
.combinations(2)
|
val yjStart = yIndex(y)
|
||||||
.collect {
|
val yjEnd = yIndex(y + 1)
|
||||||
case Array(a, b) if a != b && corners(a, b).forall(polygon.contains) =>
|
for i <- xiStart until xiEnd do for j <- yjStart until yjEnd do allowed(i)(j) = true
|
||||||
println(corners(a, b))
|
case Edge.V(x, yStart, yEnd) =>
|
||||||
(a, b)
|
val xiStart = xIndex(x)
|
||||||
|
val xiEnd = xIndex(x + 1)
|
||||||
|
val yjStart = yIndex(yStart)
|
||||||
|
val yjEnd = yIndex(yEnd + 1)
|
||||||
|
for i <- xiStart until xiEnd do for j <- yjStart until yjEnd do allowed(i)(j) = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the boolean grid into exact areas (width × height of each compressed cell).
|
||||||
|
val cellArea = Array.ofDim[Long](xs.length - 1, ys.length - 1)
|
||||||
|
for
|
||||||
|
i <- 0 until xs.length - 1
|
||||||
|
j <- 0 until ys.length - 1
|
||||||
|
do
|
||||||
|
if allowed(i)(j) then
|
||||||
|
val width = (xs(i + 1) - xs(i)).toLong
|
||||||
|
val height = (ys(j + 1) - ys(j)).toLong
|
||||||
|
cellArea(i)(j) = width * height
|
||||||
|
|
||||||
|
// 2D prefix sums let us query the total allowed area of any rectangle in O(1).
|
||||||
|
val prefix = Array.ofDim[Long](xs.length, ys.length)
|
||||||
|
for
|
||||||
|
i <- 0 until xs.length - 1
|
||||||
|
j <- 0 until ys.length - 1
|
||||||
|
do
|
||||||
|
val value = cellArea(i)(j)
|
||||||
|
prefix(i + 1)(j + 1) = value + prefix(i)(j + 1) + prefix(i + 1)(j) - prefix(i)(j)
|
||||||
|
|
||||||
|
def areaWithin(xStart: Int, yStart: Int, xEnd: Int, yEnd: Int): Long =
|
||||||
|
prefix(xEnd)(yEnd) - prefix(xStart)(yEnd) - prefix(xEnd)(yStart) + prefix(xStart)(yStart)
|
||||||
|
|
||||||
|
// Try every pair of red tiles and keep the rectangle only if every compressed cell inside
|
||||||
|
// is marked allowed (i.e. area reported by prefix sum equals the rectangle's true area).
|
||||||
|
points
|
||||||
|
.combinations(2)
|
||||||
|
.foldLeft(0L) {
|
||||||
|
case (best, Array(a, b)) =>
|
||||||
|
val xMin = min(a.x, b.x)
|
||||||
|
val xMax = max(a.x, b.x)
|
||||||
|
val yMin = min(a.y, b.y)
|
||||||
|
val yMax = max(a.y, b.y)
|
||||||
|
val area = (xMax - xMin + 1).toLong * (yMax - yMin + 1).toLong
|
||||||
|
val insideArea = areaWithin(xIndex(xMin), yIndex(yMin), xIndex(xMax + 1), yIndex(yMax + 1))
|
||||||
|
if area == insideArea then math.max(best, area) else best
|
||||||
|
case (best, _) => best
|
||||||
}
|
}
|
||||||
.map((a, b) => ((a.x - b.x + 1) * (a.y - b.y + 1)).abs)
|
|
||||||
.max
|
|
||||||
end part2
|
end part2
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue