Part 2 working

This commit is contained in:
Paul-Henri Froidmont 2025-12-12 00:46:55 +01:00
parent 327aaf4fd7
commit ef76446306
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE

View file

@ -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 =
val points = input.split('\n').map { case s"$x,$y" => Point(x.toInt, y.toInt) }
val polygon = Polygon(
(points :+ points.head).sliding(2).map { case Array(a, b) => Edge(a, b) }.toList
)
def corners(a: Point, b: Point) = case class Point(x: Int, y: Int)
List(
Point(min(a.x, b.x), min(a.y, b.y)),
Point(min(a.x, b.x), max(a.y, b.y)),
Point(max(a.x, b.x), min(a.y, b.y)),
Point(max(a.x, b.x), max(a.y, b.y))
).distinct
// println(polygon.contains(Point(9, 3))) enum Edge:
case H(y: Int, xStart: Int, xEnd: Int)
case V(x: Int, yStart: Int, yEnd: Int)
// println(polygon.edges.mkString("\n")) 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")
// println(polygon.edges.exists(_.contains(9, 3))) // Build the polygon edges out of the ordered red points.
// println(polygon.edges.exists(_.contains(9, 5))) val points = input.split('\n').map { case s"$x,$y" => Point(x.toInt, y.toInt) }
// println(polygon.contains(Point(11, 1))) val edges = (points :+ points.head).sliding(2).map { case Array(a, b) => Edge(a, b) }.toList
// println(polygon.contains(Point(11, 7))) val verticalEdges = edges.collect { case v: Edge.V => v }
// println(polygon.contains(Point(11, 8)))
// println(polygon.contains(Point(11, 9)))
// corners(Point(9, 5), Point(2, 3)).foreach(p => // Coordinate compression: keep only x or y values where something changes
// println(p) // (a boundary or its immediately-adjacent value). Each consecutive pair in `xs`/`ys`
// println(polygon.contains(p)) // represents one strip of actual tiles in the original grid.
// ) val xs = (points.map(_.x) ++ points.map(_.x + 1)).distinct.sorted
val ys = (points.map(_.y) ++ points.map(_.y + 1)).distinct.sorted
println( val xIndex = xs.zipWithIndex.toMap
points val yIndex = ys.zipWithIndex.toMap
.combinations(2)
.map { case Array(a, b) => // allowed(i)(j) tells us whether the compressed cell spanning
corners(a, b) // x in [xs(i), xs(i+1)) and y in [ys(j), ys(j+1)) belongs to the red/green region.
}.distinct.size val allowed = Array.ofDim[Boolean](xs.length - 1, ys.length - 1)
)
// Evenodd scanline: for each horizontal strip, walk the vertical edges to mark the
// interior spans between every pair of crossings.
for j <- 0 until ys.length - 1 do
val ySample = ys(j)
val crossings = verticalEdges.collect {
case Edge.V(x, yStart, yEnd) if ySample >= yStart && ySample < yEnd => x
}.sorted
crossings.grouped(2).foreach {
case List(left, right) =>
val start = xIndex(left) + 1
val end = xIndex(right)
if start < end then for i <- start until end do allowed(i)(j) = true
case _ =>
}
// Include the boundary itself so rectangles that ride along the green loop remain valid.
edges.foreach {
case Edge.H(y, xStart, xEnd) =>
val xiStart = xIndex(xStart)
val xiEnd = xIndex(xEnd + 1)
val yjStart = yIndex(y)
val yjEnd = yIndex(y + 1)
for i <- xiStart until xiEnd do for j <- yjStart until yjEnd do allowed(i)(j) = true
case Edge.V(x, yStart, yEnd) =>
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 points
.combinations(2) .combinations(2)
.collect { .foldLeft(0L) {
case Array(a, b) if a != b && corners(a, b).forall(polygon.contains) => case (best, Array(a, b)) =>
println(corners(a, b)) val xMin = min(a.x, b.x)
(a, b) 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