Skip to content

Commit c8485a0

Browse files
committed
2023 day 17 use givens rather than globals
1 parent 139491e commit c8485a0

File tree

1 file changed

+61
-51
lines changed

1 file changed

+61
-51
lines changed

2023/src/day23.scala

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import locations.Directory.currentDir
77
import inputs.Input.loadFileSync
88

99
@main def part1: Unit =
10-
val ans = longestDownhillHike(start, 0)
11-
println(s"The solution is $ans")
10+
given maze: Maze = Maze(loadInput())
11+
println(s"The solution is $longestDownhillHike")
1212

1313
@main def part2: Unit =
14-
val ans = longestHike(indexOf(start), BitSet.empty, 0)
15-
println(s"The solution is $ans")
14+
given maze: Maze = Maze(loadInput())
15+
println(s"The solution is $longestHike")
1616

1717
def loadInput(): Vector[Vector[Char]] = Vector.from:
1818
val file = loadFileSync(s"$currentDir/../input/day23")
@@ -44,68 +44,78 @@ case class Point(x: Int, y: Int):
4444
case Dir.E => copy(x = x + 1)
4545
case Dir.W => copy(x = x - 1)
4646

47-
val grid = loadInput()
47+
case class Maze(grid: Vector[Vector[Char]]):
4848

49-
val xRange = grid.head.indices
50-
val yRange = grid.indices
49+
def apply(p: Point): Char = grid(p.y)(p.x)
5150

52-
def points: Iterator[Point] = for
53-
y <- yRange.iterator
54-
x <- xRange.iterator
55-
yield Point(x, y)
51+
val xRange = grid.head.indices
52+
val yRange = grid.indices
5653

57-
val slopes = Map.from[Point, Dir]:
58-
points.collect:
59-
case p if grid(p.y)(p.x) == '^' => p -> Dir.N
60-
case p if grid(p.y)(p.x) == 'v' => p -> Dir.S
61-
case p if grid(p.y)(p.x) == '>' => p -> Dir.E
62-
case p if grid(p.y)(p.x) == '<' => p -> Dir.W
54+
def points: Iterator[Point] = for
55+
y <- yRange.iterator
56+
x <- xRange.iterator
57+
yield Point(x, y)
6358

64-
val walkable = points.filter(p => grid(p.y)(p.x) != '#').toSet
65-
val start = walkable.minBy(_.y)
66-
val end = walkable.maxBy(_.y)
59+
val walkable = points.filter(p => grid(p.y)(p.x) != '#').toSet
60+
val start = walkable.minBy(_.y)
61+
val end = walkable.maxBy(_.y)
6762

68-
val nodes: Set[Point] = walkable.filter: p =>
69-
Dir.values.map(p.move).count(walkable) > 2
70-
.toSet + start + end
63+
val slopes = Map.from[Point, Dir]:
64+
points.collect:
65+
case p if apply(p) == '^' => p -> Dir.N
66+
case p if apply(p) == 'v' => p -> Dir.S
67+
case p if apply(p) == '>' => p -> Dir.E
68+
case p if apply(p) == '<' => p -> Dir.W
7169

72-
def next(pos: Point, dir: Dir): List[(Point, Dir)] =
70+
val nodes: Set[Point] = walkable.filter: p =>
71+
Dir.values.map(p.move).count(walkable) > 2
72+
.toSet + start + end
73+
74+
75+
def next(pos: Point, dir: Dir)(using maze: Maze): List[(Point, Dir)] =
7376
for
7477
d <- List(dir, dir.turnRight, dir.turnLeft)
7578
p = pos.move(d)
76-
if slopes.get(p).forall(_ == d)
77-
if walkable(p)
79+
if maze.slopes.get(p).forall(_ == d)
80+
if maze.walkable(p)
7881
yield p -> d
7982

80-
def nodesFrom(pos: Point) = List.from[(Point, Int)]:
83+
def nodesFrom(pos: Point)(using maze: Maze) = List.from[(Point, Int)]:
8184
def search(p: Point, d: Dir, dist: Int): Option[(Point, Int)] =
8285
next(p, d) match
83-
case (p, d) :: Nil if nodes(p) => Some(p, dist + 1)
86+
case (p, d) :: Nil if maze.nodes(p) => Some(p, dist + 1)
8487
case (p, d) :: Nil => search(p, d, dist + 1)
8588
case _ => None
8689

8790
Dir.values.flatMap(next(pos, _)).distinct.flatMap(search(_, _, 1))
8891

89-
def longestDownhillHike(pos: Point, dist: Int): Int =
90-
if pos == end then dist else
91-
nodesFrom(pos).foldLeft(0):
92-
case (max, (n, d)) => max.max(longestDownhillHike(n, dist + d))
93-
94-
type Node = Int
95-
val indexOf: Map[Point, Node] =
96-
nodes.toList.sortBy(_.dist(start)).zipWithIndex.toMap
97-
98-
val fullAdj: Map[Node, List[(Node, Int)]] =
99-
nodes.toList.flatMap: p1 =>
100-
nodesFrom(p1).flatMap: (p2, d) =>
101-
val forward = indexOf(p1) -> (indexOf(p2), d)
102-
val reverse = indexOf(p2) -> (indexOf(p1), d)
103-
List(forward, reverse)
104-
.groupMap(_._1)(_._2)
105-
106-
def longestHike(node: Node, visited: BitSet, dist: Int): Int =
107-
if node == indexOf(end) then dist else
108-
fullAdj(node).foldLeft(0):
109-
case (max, (n, d)) =>
110-
if visited(n) then max else
111-
max.max(longestHike(n, visited + n, dist + d))
92+
def longestDownhillHike(using maze: Maze): Int =
93+
def search(pos: Point, dist: Int)(using maze: Maze): Int =
94+
if pos == maze.end then dist else
95+
nodesFrom(pos).foldLeft(0):
96+
case (max, (n, d)) => max.max(search(n, dist + d))
97+
98+
search(maze.start, 0)
99+
100+
def longestHike(using maze: Maze): Int =
101+
type Index = Int
102+
103+
val indexOf: Map[Point, Index] =
104+
maze.nodes.toList.sortBy(_.dist(maze.start)).zipWithIndex.toMap
105+
106+
val adjacent: Map[Index, List[(Index, Int)]] =
107+
maze.nodes.toList.flatMap: p1 =>
108+
nodesFrom(p1).flatMap: (p2, d) =>
109+
val forward = indexOf(p1) -> (indexOf(p2), d)
110+
val reverse = indexOf(p2) -> (indexOf(p1), d)
111+
List(forward, reverse)
112+
.groupMap(_._1)(_._2)
113+
114+
def search(node: Index, visited: BitSet, dist: Int): Int =
115+
if node == indexOf(maze.end) then dist else
116+
adjacent(node).foldLeft(0):
117+
case (max, (n, d)) =>
118+
if visited(n) then max else
119+
max.max(search(n, visited + n, dist + d))
120+
121+
search(indexOf(maze.start), BitSet.empty, 0)

0 commit comments

Comments
 (0)