aoc2023/Day17.scala

107 lines
3.1 KiB
Scala
Raw Normal View History

2023-12-17 22:49:58 +00:00
package aoc.day17
import aoc._
import scala.collection.mutable
val board = lines.map(Parser.parse(Parser.row, _).get).map(_.toArray).toArray
val sumRows = board.map(_.scanLeft(0)(_ + _))
val sumCols = (0 until board.length)
.scanLeft(new Array[Int](board(0).length)) { (last, row) =>
last.toIterator.zipWithIndex.map { (last, col) => last + board(row)(col) }.toArray
}
.toArray
object Dir:
private[Dir] val dx = Array(-1, 0, 1, 0)
private[Dir] val dy = Array(0, 1, 0, -1)
import Dir._
enum Dir:
case Up, Right, Down, Left
def apply(x: Int, y: Int) =
val k = this.ordinal
(x + dx(k), y + dy(k))
inline def times(n: Int)(x: Int, y: Int) =
val k = this.ordinal
val (nx, ny) = (x + n * dx(k), y + n * dy(k))
if inside(nx, ny) then
val dist = this match
case Up => sumCols(x)(y) - sumCols(nx)(y)
case Right => sumRows(x)(ny + 1) - sumRows(x)(y + 1)
case Down => sumCols(nx + 1)(y) - sumCols(x + 1)(y)
case Left => sumRows(x)(y) - sumRows(x)(ny)
Some(State(nx, ny, this), dist)
else None
object Parser extends CommonParser:
val digit = """\d""".r ^^ { _.toInt }
val row = rep1(digit)
end Parser
inline def inside(x: Int, y: Int) =
x >= 0 && x < board.length && y >= 0 && y < board(0).length
case class State(x: Int, y: Int, dir: Dir):
inline def next(ndir: Dir)(using steps: Seq[Int]) =
steps.toIterator
.flatMap(ndir.times(_)(x, y))
inline def left(using Seq[Int]) = next(Dir.fromOrdinal((dir.ordinal + 3) % 4))
inline def right(using Seq[Int]) = next(Dir.fromOrdinal((dir.ordinal + 1) % 4))
def all(using Seq[Int]) = left ++ right
object Walk:
// private val visited = mutable.Map.empty[State, Int]
private object visited:
private inline def idx(x: Int, y: Int, dir: Dir) = x * board(0).length * 4 + y * 4 + dir.ordinal
private val arr = Array.fill(board.length * board(0).length * 4)(1_000_000_000)
inline def apply(s: State) = arr(idx(s.x, s.y, s.dir))
inline def +=(inline p: (State, Int)) = p match
case (s, v) => arr(idx(s.x, s.y, s.dir)) = v
private val q =
mutable.PriorityQueue.empty[(Int, State)](using scala.math.Ordering.by((v, _) => -v))
def go()(using Seq[Int]): Option[Int] =
Seq(Up, Down, Left, Right).foreach: dir =>
visited += (State(0, 0, dir) -> 0)
q += ((0, State(0, 0, dir)))
@scala.annotation.tailrec
def loop(): Option[Int] =
if q.isEmpty then None
else
val (d, s) = q.dequeue()
if d != visited(s) then loop()
else if s.x == board.length - 1 && s.y == board(0).length - 1 then Some(d)
else
for (ns, delta) <- s.all do
val nd = d + delta
val old = visited(ns)
if old > nd then
visited += (ns -> nd)
q += ((nd, ns))
loop()
var res = loop()
res
// part 1
def solve(using steps: Seq[Int]) =
val res =
Walk
.go()
.get
println(res)
def part1 = solve(using (1 to 3))
def part2 = solve(using (4 to 10))
@main def Day17(part: Int) = part match
case 1 => part1
case 2 => part2