@@ -48,17 +48,21 @@ case class Maze(grid: Vector[Vector[Char]]):
48
48
49
49
def apply (p : Point ): Char = grid(p.y)(p.x)
50
50
51
- val xRange = grid.head.indices
52
- val yRange = grid.indices
51
+ val xRange : Range = grid.head.indices
52
+ val yRange : Range = grid.indices
53
53
54
54
def points : Iterator [Point ] = for
55
55
y <- yRange.iterator
56
56
x <- xRange.iterator
57
57
yield Point (x, y)
58
58
59
- val walkable = points.filter(p => grid(p.y)(p.x) != '#' ).toSet
60
- val start = walkable.minBy(_.y)
61
- val end = walkable.maxBy(_.y)
59
+ val walkable : Set [Point ] = points.filter(p => grid(p.y)(p.x) != '#' ).toSet
60
+ val start : Point = walkable.minBy(_.y)
61
+ val end : Point = walkable.maxBy(_.y)
62
+
63
+ val junctions : Set [Point ] = walkable.filter: p =>
64
+ Dir .values.map(p.move).count(walkable) > 2
65
+ .toSet + start + end
62
66
63
67
val slopes = Map .from[Point , Dir ]:
64
68
points.collect:
@@ -67,32 +71,30 @@ case class Maze(grid: Vector[Vector[Char]]):
67
71
case p if apply(p) == '>' => p -> Dir .E
68
72
case p if apply(p) == '<' => p -> Dir .W
69
73
70
- val nodes : Set [ Point ] = walkable.filter : p =>
71
- Dir .values.map(p.move).count(walkable) > 2
72
- .toSet + start + end
73
-
74
+ def connectedJunctions ( pos : Point )( using maze : Maze ) = List .from[( Point , Int )] :
75
+ def walk ( pos : Point , dir : Dir ) : Option [ Point ] =
76
+ val p = pos.move(dir)
77
+ Option .when(maze.walkable(p) && maze.slopes.get(p).forall(_ == dir))(p)
74
78
75
- def next (pos : Point , dir : Dir )(using maze : Maze ): List [(Point , Dir )] =
76
- for
77
- d <- List (dir, dir.turnRight, dir.turnLeft)
78
- p = pos.move(d)
79
- if maze.slopes.get(p).forall(_ == d)
80
- if maze.walkable(p)
81
- yield p -> d
79
+ def search (pos : Point , facing : Dir , dist : Int ): Option [(Point , Int )] =
80
+ if maze.junctions.contains(pos) then Some (pos, dist) else
81
+ val adjacentSearch = for
82
+ nextFacing <- LazyList (facing, facing.turnRight, facing.turnLeft)
83
+ nextPos <- walk(pos, nextFacing)
84
+ yield search(nextPos, nextFacing, dist + 1 )
82
85
83
- def nodesFrom (pos : Point )(using maze : Maze ) = List .from[(Point , Int )]:
84
- def search (p : Point , d : Dir , dist : Int ): Option [(Point , Int )] =
85
- next(p, d) match
86
- case (p, d) :: Nil if maze.nodes(p) => Some (p, dist + 1 )
87
- case (p, d) :: Nil => search(p, d, dist + 1 )
88
- case _ => None
86
+ if adjacentSearch.size == 1 then adjacentSearch.head else None
89
87
90
- Dir .values.flatMap(next(pos, _)).distinct.flatMap(search(_, _, 1 ))
88
+ for
89
+ d <- Dir .values
90
+ p <- walk(pos, d)
91
+ junction <- search(p, d, 1 )
92
+ yield junction
91
93
92
94
def longestDownhillHike (using maze : Maze ): Int =
93
95
def search (pos : Point , dist : Int )(using maze : Maze ): Int =
94
96
if pos == maze.end then dist else
95
- nodesFrom (pos).foldLeft(0 ):
97
+ connectedJunctions (pos).foldLeft(0 ):
96
98
case (max, (n, d)) => max.max(search(n, dist + d))
97
99
98
100
search(maze.start, 0 )
@@ -101,21 +103,21 @@ def longestHike(using maze: Maze): Int =
101
103
type Index = Int
102
104
103
105
val indexOf : Map [Point , Index ] =
104
- maze.nodes .toList.sortBy(_.dist(maze.start)).zipWithIndex.toMap
106
+ maze.junctions .toList.sortBy(_.dist(maze.start)).zipWithIndex.toMap
105
107
106
108
val adjacent : Map [Index , List [(Index , Int )]] =
107
- maze.nodes .toList.flatMap: p1 =>
108
- nodesFrom (p1).flatMap: (p2, d) =>
109
+ maze.junctions .toList.flatMap: p1 =>
110
+ connectedJunctions (p1).flatMap: (p2, d) =>
109
111
val forward = indexOf(p1) -> (indexOf(p2), d)
110
112
val reverse = indexOf(p2) -> (indexOf(p1), d)
111
113
List (forward, reverse)
112
114
.groupMap(_._1)(_._2)
113
115
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 ))
116
+ def search (junction : Index , visited : BitSet , totalDist : Int ): Int =
117
+ if junction == indexOf(maze.end) then totalDist else
118
+ adjacent(junction ).foldLeft(0 ):
119
+ case (longest , (nextJunct, dist )) =>
120
+ if visited(nextJunct ) then longest else
121
+ longest .max(search(nextJunct , visited + nextJunct, totalDist + dist ))
120
122
121
123
search(indexOf(maze.start), BitSet .empty, 0 )
0 commit comments