From 82c7c9fdd9d128c074a7366221ef33359dff60e4 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 7 Dec 2024 19:04:29 +0100 Subject: [PATCH] Improve (?) Day 06 --- aoc/src/dev/ctsk/aoc/Grid.scala | 55 ++++++++++++++ aoc/src/dev/ctsk/aoc/Parse.scala | 6 ++ aoc/src/dev/ctsk/aoc/days/Day06.scala | 100 +++++++++++++------------- 3 files changed, 109 insertions(+), 52 deletions(-) create mode 100644 aoc/src/dev/ctsk/aoc/Grid.scala create mode 100644 aoc/src/dev/ctsk/aoc/Parse.scala diff --git a/aoc/src/dev/ctsk/aoc/Grid.scala b/aoc/src/dev/ctsk/aoc/Grid.scala new file mode 100644 index 0000000..c7388dd --- /dev/null +++ b/aoc/src/dev/ctsk/aoc/Grid.scala @@ -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 diff --git a/aoc/src/dev/ctsk/aoc/Parse.scala b/aoc/src/dev/ctsk/aoc/Parse.scala new file mode 100644 index 0000000..a1c25c4 --- /dev/null +++ b/aoc/src/dev/ctsk/aoc/Parse.scala @@ -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 diff --git a/aoc/src/dev/ctsk/aoc/days/Day06.scala b/aoc/src/dev/ctsk/aoc/days/Day06.scala index 9b04915..485e623 100644 --- a/aoc/src/dev/ctsk/aoc/days/Day06.scala +++ b/aoc/src/dev/ctsk/aoc/days/Day06.scala @@ -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)) )