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)))
|
||||
|
||||
def part1(input: String): Long =
|
||||
val tiles = input.split('\n').map { case s"$x,$y" => (x = x.toLong, y = y.toLong) }
|
||||
tiles
|
||||
input
|
||||
.split('\n').map { case s"$x,$y" => (x = x.toLong, y = y.toLong) }
|
||||
.combinations(2)
|
||||
.collect {
|
||||
case Array(a, b) if a != b =>
|
||||
((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)
|
||||
.collect { case Array(a, b) => ((a.x - b.x + 1) * (a.y - b.y + 1)).abs }
|
||||
.max
|
||||
|
||||
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 polygon = Polygon(
|
||||
(points :+ points.head).sliding(2).map { case Array(a, b) => Edge(a, b) }.toList
|
||||
)
|
||||
val edges = (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) =
|
||||
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
|
||||
// Coordinate compression: keep only x or y values where something changes
|
||||
// (a boundary or its immediately-adjacent value). Each consecutive pair in `xs`/`ys`
|
||||
// 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(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)))
|
||||
// println(polygon.edges.exists(_.contains(9, 5)))
|
||||
// println(polygon.contains(Point(11, 1)))
|
||||
// println(polygon.contains(Point(11, 7)))
|
||||
// println(polygon.contains(Point(11, 8)))
|
||||
// println(polygon.contains(Point(11, 9)))
|
||||
|
||||
// corners(Point(9, 5), Point(2, 3)).foreach(p =>
|
||||
// println(p)
|
||||
// println(polygon.contains(p))
|
||||
// )
|
||||
|
||||
println(
|
||||
points
|
||||
.combinations(2)
|
||||
.map { case Array(a, b) =>
|
||||
corners(a, b)
|
||||
}.distinct.size
|
||||
)
|
||||
points
|
||||
.combinations(2)
|
||||
.collect {
|
||||
case Array(a, b) if a != b && corners(a, b).forall(polygon.contains) =>
|
||||
println(corners(a, b))
|
||||
(a, b)
|
||||
// Even–odd 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
|
||||
.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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue