Scala3
forgot to post this
import Area.*
import Dir.*
enum Dir(num: Int, diff: (Int, Int)):
val n = num
val d = diff
case Up extends Dir(3, (0, -1))
case Down extends Dir(1, (0, 1))
case Left extends Dir(2, (-1, 0))
case Right extends Dir(0, (1, 0))
def opposite = Dir.from(n + 2)
object Dir:
def from(n: Int): Dir = Dir.all.filter(_.n == n % 4).ensuring(_.size == 1).head
def all = List(Up, Down, Left, Right)
enum Area:
case Inside, Outside, Loop
case class Pos(x: Int, y: Int)
type Landscape = Map[Pos, Pipe]
type Loop = Map[Pos, LoopPiece]
def walk(p: Pos, d: Dir): Pos = Pos(p.x + d.d._1, p.y + d.d._2)
val pipeMap = Map('|' -> List(Up, Down), '-' -> List(Left, Right), 'L' -> List(Up, Right), 'J' -> List(Up, Left), 'F' -> List(Right, Down), '7' -> List(Left, Down))
case class Pipe(neighbors: List[Dir])
case class LoopPiece(from: Dir, to: Dir):
def left: List[Dir] = ((from.n + 1) until (if to.n < from.n then to.n + 4 else to.n)).map(Dir.from).toList
def right: List[Dir] = LoopPiece(to, from).left
def parse(a: List[String]): (Pos, Landscape) =
val pipes = for (r, y) <- a.zipWithIndex; (v, x) <- r.zipWithIndex; p <- pipeMap.get(v) yield Pos(x, y) -> Pipe(p)
val start = for (r, y) <- a.zipWithIndex; (v, x) <- r.zipWithIndex if v == 'S' yield Pos(x, y)
(start.head, pipes.toMap)
def walkLoop(start: Pos, l: Landscape): Loop =
@tailrec def go(pos: Pos, last_dir: Dir, acc: Loop): Loop =
if pos == start then acc else
val dir = l(pos).neighbors.filter(_ != last_dir.opposite).ensuring(_.size == 1).head
go(walk(pos, dir), dir, acc + (pos -> LoopPiece(last_dir.opposite, dir)))
Dir.all.filter(d => l.get(walk(start, d)).exists(p => p.neighbors.contains(d.opposite))) match
case List(start_dir, return_dir) => go(walk(start, start_dir), start_dir, Map(start -> LoopPiece(return_dir, start_dir)))
case _ => Map()
def task1(a: List[String]): Long =
walkLoop.tupled(parse(a)).size.ensuring(_ % 2 == 0) / 2
def task2(a: List[String]): Long =
val loop = walkLoop.tupled(parse(a))
val ys = a.indices
val xs = a.head.indices
val points = (for x <- xs; y <- ys yield Pos(x, y)).toSet
// floodfill
@tailrec def go(outside: Set[Pos], q: List[Pos]): Set[Pos] =
if q.isEmpty then outside else
val nbs = Dir.all.map(walk(q.head, _)).filter(points.contains(_)).filter(!outside.contains(_))
go(outside ++ nbs, nbs ++ q.tail)
// start by floodfilling from the known outside: beyond the array bounds
val boundary = ys.flatMap(y => List(Pos(-1, y), Pos(xs.end, y))) ++ xs.flatMap(x => List(Pos(x, -1), Pos(x, ys.end)))
val r = go(boundary.toSet ++ loop.keySet, boundary.toList)
// check on which side of the pipe the outside is, then continue floodfill from there
val xsl = List[LoopPiece => List[Dir]](_.left, _.right).map(side => loop.flatMap((p, l) => side(l).map(d => walk(p, d))).filter(!loop.contains(_)).toSet).map(a => a -> a.intersect(r).size).ensuring(_.exists(_._2 == 0)).filter(_._2 != 0).head._1
(points -- go(r ++ xsl, xsl.toList)).size