Improve (?) Day 06
This commit is contained in:
55
aoc/src/dev/ctsk/aoc/Grid.scala
Normal file
55
aoc/src/dev/ctsk/aoc/Grid.scala
Normal file
@@ -0,0 +1,55 @@
|
||||
package dev.ctsk.aoc
|
||||
|
||||
case class Point(x: Int, y: Int):
|
||||
def +(o: Point): Point = Point(x + o.x, y + o.y)
|
||||
def *(n: Int): Point = Point(n * x, n * y)
|
||||
|
||||
enum Direction:
|
||||
case Up, Right, Down, Left
|
||||
def turnRight: Direction = Direction.fromOrdinal((ordinal + 1) % 4)
|
||||
def toPoint: Point =
|
||||
this match
|
||||
case Up => Point(-1, 0)
|
||||
case Right => Point(0, 1)
|
||||
case Down => Point(1, 0)
|
||||
case Left => Point(0, -1)
|
||||
|
||||
def apply(p: Point): Point = p + toPoint
|
||||
def applyN(p: Point, n: Int): Point = p + toPoint * n
|
||||
|
||||
def flip: Direction =
|
||||
this match
|
||||
case Up => Down
|
||||
case Right => Left
|
||||
case Down => Up
|
||||
case Left => Right
|
||||
|
||||
case class Pose(pos: Point, dir: Direction):
|
||||
def turnRight: Pose = Pose(pos, dir.turnRight)
|
||||
def step: Pose = Pose(dir(pos), dir)
|
||||
def step(n: Int): Pose = Pose(dir.applyN(pos, n), dir)
|
||||
|
||||
class Grid[A](val data: Array[Array[A]]):
|
||||
def height: Int = data.length
|
||||
def width: Int = data(0).length
|
||||
|
||||
def apply(p: Point): Option[A] =
|
||||
if p.x < 0 | p.y < 0 | p.x >= height | p.y >= width
|
||||
then None
|
||||
else Some(data(p.x)(p.y))
|
||||
|
||||
def update(p: Point, a: A): Unit = data(p.x)(p.y) = a
|
||||
|
||||
def find(f: A => Boolean): IndexedSeq[Point] =
|
||||
for
|
||||
i <- data.indices
|
||||
j <- data(i).indices
|
||||
if f(data(i)(j))
|
||||
yield Point(i, j)
|
||||
|
||||
def findFirst(f: A => Boolean): Option[Point] =
|
||||
data.zipWithIndex.flatMap { (row, x) =>
|
||||
row.zipWithIndex.collect { case (a, y) if f(a) => Point(x, y) }
|
||||
}.headOption
|
||||
|
||||
def count(f: A => Boolean): Int = data.map(_.count(f)).sum
|
||||
6
aoc/src/dev/ctsk/aoc/Parse.scala
Normal file
6
aoc/src/dev/ctsk/aoc/Parse.scala
Normal file
@@ -0,0 +1,6 @@
|
||||
package dev.ctsk.aoc
|
||||
|
||||
def NAT_REGEX = """(\d+)""".r
|
||||
|
||||
def longs(string: String): Vector[Long] =
|
||||
NAT_REGEX.findAllIn(string).map(_.toLong).toVector
|
||||
@@ -1,69 +1,65 @@
|
||||
package dev.ctsk.aoc.days
|
||||
|
||||
import dev.ctsk.aoc._
|
||||
import scala.compiletime.ops.boolean
|
||||
import scala.util.boundary.break
|
||||
import scala.compiletime.ops.double
|
||||
import dev.ctsk.aoc.Direction._
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
import scala.collection.parallel.CollectionConverters._
|
||||
|
||||
object Day06 extends Solver(6):
|
||||
|
||||
def patrol(
|
||||
grid: Array[Array[Char]],
|
||||
start: (Int, Int),
|
||||
obstacle: (Int, Int)
|
||||
): (Boolean, () => Vector[(Int, Int)]) =
|
||||
var seen =
|
||||
Array.fill(4)(Array.fill(grid.length)(Array.fill(grid(0).length)(false)))
|
||||
|
||||
def visited() =
|
||||
(for
|
||||
row <- 0 until grid.length
|
||||
col <- 0 until grid(0).length
|
||||
if (0 until 4).exists(v => seen(v)(row)(col))
|
||||
yield (row, col)).toVector
|
||||
|
||||
var dirs = Vector((-1, 0), (0, 1), (1, 0), (0, -1))
|
||||
var dir = 0
|
||||
var pos = start
|
||||
seen(dir)(pos._1)(pos._2) = true
|
||||
|
||||
while true do
|
||||
var next = (pos._1 + dirs(dir)._1, pos._2 + dirs(dir)._2)
|
||||
if next._1 < 0 || next._1 >= grid.length
|
||||
|| next._2 < 0 || next._2 >= grid(0).length
|
||||
then return (false, visited)
|
||||
else if grid(next._1)(next._2) == '#' || next == obstacle
|
||||
then dir = (dir + 1) % 4
|
||||
else if seen(dir)(next._1)(next._2)
|
||||
then return (true, visited)
|
||||
else
|
||||
seen(dir)(next._1)(next._2) = true
|
||||
pos = next
|
||||
|
||||
(false, visited)
|
||||
def analyse(grid: Grid[Char]): Array[Array[Array[Int]]] =
|
||||
val skipMap = Array.fill(grid.height, grid.width, 4)(-1)
|
||||
for
|
||||
obstacle <- grid.find(_ == '#')
|
||||
d: Direction <- Seq(Up, Down, Right, Left)
|
||||
(pt, dist) <- Iterator
|
||||
.iterate(d(obstacle))(d(_))
|
||||
.takeWhile(pt => grid(pt).exists(_ != '#'))
|
||||
.zipWithIndex
|
||||
do skipMap(pt.x)(pt.y)(d.flip.ordinal) = dist
|
||||
skipMap
|
||||
|
||||
def run(input: os.ReadablePath): (Timings, Solution) =
|
||||
val lines = os.read.lines(input).map(_.toArray).toArray
|
||||
val grid = Grid(os.read.lines(input).map(_.toArray).toArray)
|
||||
val (pre_time, start) = timed { grid.findFirst(_ == '^').get }
|
||||
|
||||
var start =
|
||||
(for
|
||||
i <- 0 until lines.length
|
||||
j <- 0 until lines(i).length
|
||||
if lines(i)(j) == '^'
|
||||
yield (i, j)).head
|
||||
@tailrec def trace(start: Pose, acc: Set[Point] = Set(start)): Set[Point] =
|
||||
val next = start.step
|
||||
grid(next.pos) match
|
||||
case Some('#') => trace(start.turnRight, acc)
|
||||
case Some(_) => trace(next, acc + next.pos)
|
||||
case None => acc
|
||||
|
||||
val (p1_time, visited) = timed { patrol(lines, start, (-1, -1))._2() }
|
||||
val p1_solution = visited.length
|
||||
val (p1_time, guardRoute) = timed { trace(Pose(pos = start, dir = Up)) }
|
||||
val p1_solution = guardRoute.size
|
||||
|
||||
val (p2_time, p2_solution) =
|
||||
timed {
|
||||
visited.par.count((i, j) =>
|
||||
(i, j) != start && patrol(lines, start, (i, j))._1
|
||||
)
|
||||
val skipMap = analyse(grid)
|
||||
|
||||
def loops(start: Pose, obstacle: Point): Boolean =
|
||||
val seen = mutable.Set(start)
|
||||
@tailrec def rec(cur: Pose): Boolean =
|
||||
val next = cur.step
|
||||
grid(next.pos) match
|
||||
case Some('#') =>
|
||||
if seen.contains(cur) then return true
|
||||
seen += cur
|
||||
rec(cur.turnRight)
|
||||
case Some(_) =>
|
||||
if next.pos == obstacle then return rec(cur.turnRight)
|
||||
if next.pos.x == obstacle.x || next.pos.y == obstacle.y
|
||||
then rec(next)
|
||||
else
|
||||
val steps = skipMap(cur.pos.x)(cur.pos.y)(cur.dir.ordinal)
|
||||
steps != -1 && rec(cur.step(steps))
|
||||
case None => false
|
||||
rec(start)
|
||||
|
||||
guardRoute.filter(_ != start).count(loops(Pose(start, Up), _))
|
||||
}
|
||||
|
||||
return (
|
||||
Timings(0, p1_time, p2_time),
|
||||
(
|
||||
Timings(pre_time, p1_time, p2_time),
|
||||
Solution(Int.box(p1_solution), Int.box(p2_solution))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user