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
|
package dev.ctsk.aoc.days
|
||||||
|
|
||||||
import dev.ctsk.aoc._
|
import dev.ctsk.aoc._
|
||||||
import scala.compiletime.ops.boolean
|
import dev.ctsk.aoc.Direction._
|
||||||
import scala.util.boundary.break
|
import scala.annotation.tailrec
|
||||||
import scala.compiletime.ops.double
|
import scala.collection.mutable
|
||||||
import scala.collection.parallel.CollectionConverters._
|
import scala.collection.parallel.CollectionConverters._
|
||||||
|
|
||||||
object Day06 extends Solver(6):
|
object Day06 extends Solver(6):
|
||||||
|
def analyse(grid: Grid[Char]): Array[Array[Array[Int]]] =
|
||||||
def patrol(
|
val skipMap = Array.fill(grid.height, grid.width, 4)(-1)
|
||||||
grid: Array[Array[Char]],
|
for
|
||||||
start: (Int, Int),
|
obstacle <- grid.find(_ == '#')
|
||||||
obstacle: (Int, Int)
|
d: Direction <- Seq(Up, Down, Right, Left)
|
||||||
): (Boolean, () => Vector[(Int, Int)]) =
|
(pt, dist) <- Iterator
|
||||||
var seen =
|
.iterate(d(obstacle))(d(_))
|
||||||
Array.fill(4)(Array.fill(grid.length)(Array.fill(grid(0).length)(false)))
|
.takeWhile(pt => grid(pt).exists(_ != '#'))
|
||||||
|
.zipWithIndex
|
||||||
def visited() =
|
do skipMap(pt.x)(pt.y)(d.flip.ordinal) = dist
|
||||||
(for
|
skipMap
|
||||||
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 run(input: os.ReadablePath): (Timings, Solution) =
|
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 =
|
@tailrec def trace(start: Pose, acc: Set[Point] = Set(start)): Set[Point] =
|
||||||
(for
|
val next = start.step
|
||||||
i <- 0 until lines.length
|
grid(next.pos) match
|
||||||
j <- 0 until lines(i).length
|
case Some('#') => trace(start.turnRight, acc)
|
||||||
if lines(i)(j) == '^'
|
case Some(_) => trace(next, acc + next.pos)
|
||||||
yield (i, j)).head
|
case None => acc
|
||||||
|
|
||||||
val (p1_time, visited) = timed { patrol(lines, start, (-1, -1))._2() }
|
val (p1_time, guardRoute) = timed { trace(Pose(pos = start, dir = Up)) }
|
||||||
val p1_solution = visited.length
|
val p1_solution = guardRoute.size
|
||||||
|
|
||||||
val (p2_time, p2_solution) =
|
val (p2_time, p2_solution) =
|
||||||
timed {
|
timed {
|
||||||
visited.par.count((i, j) =>
|
val skipMap = analyse(grid)
|
||||||
(i, j) != start && patrol(lines, start, (i, j))._1
|
|
||||||
)
|
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))
|
Solution(Int.box(p1_solution), Int.box(p2_solution))
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user