98 lines
2.8 KiB
Scala
98 lines
2.8 KiB
Scala
package aoc.day23
|
|
|
|
import aoc._
|
|
import aoc.direction._
|
|
import scala.collection.mutable
|
|
|
|
enum Cell:
|
|
case Path, Forest
|
|
case Slope(dir: Dir)
|
|
import Cell._
|
|
|
|
object Parser extends CommonParser:
|
|
import Dir._
|
|
val slope = ('^' ^^^ Up | 'v' ^^^ Down | '<' ^^^ Left | '>' ^^^ Right) ^^ (Slope(_))
|
|
val cell = '.' ^^^ Path | '#' ^^^ Forest | slope
|
|
|
|
val line = rep(cell) ^^ (_.toArray)
|
|
|
|
val bStr = lines.toArray
|
|
val board = bStr.map(Parser.parse(Parser.line, _).get).toArray
|
|
|
|
def inside(x: Int, y: Int) =
|
|
x >= 0 && x < board.length && y >= 0 && y < board(0).length
|
|
|
|
extension [K](m: mutable.Set[K])
|
|
inline def withFound[T](k: K)(inline body: => T): T =
|
|
m += k
|
|
val res = body
|
|
m -= k
|
|
res
|
|
|
|
// part 1
|
|
|
|
object longestPath:
|
|
var bestPathFound = 0
|
|
val visited = mutable.Set.empty[(Int, Int)]
|
|
val intersections = mutable.Map.empty[(Int, Int), Seq[((Int, Int), Int)]]
|
|
val cache = mutable.Map.empty[(Long, Int, Int), Option[Int]]
|
|
|
|
def options(followSlopes: Boolean)(x: Int, y: Int) =
|
|
val nexts = board(x)(y) match
|
|
case Path => Dir.all.iterator
|
|
case Forest => Iterator.empty
|
|
case Slope(dir) => if followSlopes then Iterator(dir) else Dir.all.iterator
|
|
nexts
|
|
.map(_(x, y))
|
|
.filter(inside(_, _))
|
|
.filter(board(_)(_) != Forest)
|
|
.toSeq
|
|
|
|
@scala.annotation.tailrec
|
|
def walkUntilIntersection(
|
|
followSlopes: Boolean
|
|
)(last: (Int, Int), x: Int, y: Int, accum: Int): Option[((Int, Int), Int)] =
|
|
if x == board.size - 1 then return Some(((x, y), accum))
|
|
val options = this.options(followSlopes)(x, y).filter(_ != last)
|
|
options match
|
|
case Seq() => None
|
|
case Seq((nx, ny)) => walkUntilIntersection(followSlopes)((x, y), nx, ny, accum + 1)
|
|
case _ => Some(((x, y), accum))
|
|
|
|
def getChoices(followSlopes: Boolean)(x: Int, y: Int) =
|
|
intersections.getOrElseUpdate(
|
|
(x, y), {
|
|
this.options(followSlopes)(x, y).flatMap(walkUntilIntersection(followSlopes)((x, y), _, _, 1))
|
|
}
|
|
)
|
|
|
|
def apply(followSlopes: Boolean)(
|
|
x: Int,
|
|
y: Int,
|
|
accum: Int = 0
|
|
): Option[Int] =
|
|
if x == board.size - 1 then Some(accum)
|
|
else
|
|
getChoices(followSlopes)(x, y) match
|
|
case Seq() => None
|
|
case options =>
|
|
// println(s"$x $y $accum $options")
|
|
visited.withFound((x, y)):
|
|
val res = options
|
|
.filter((p, _) => !visited.contains(p))
|
|
.flatMap:
|
|
case ((nx, ny), dist) =>
|
|
longestPath(followSlopes)(nx, ny, accum + dist)
|
|
.maxOption
|
|
res
|
|
|
|
def part1 =
|
|
println(longestPath(true)(0, board(0).indexWhere(_ != Forest)))
|
|
|
|
def part2 =
|
|
println(longestPath(false)(0, board(0).indexWhere(_ != Forest)))
|
|
|
|
@main def Day23(part: Int) = part match
|
|
case 1 => part1
|
|
case 2 => part2
|