Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5128b52

Browse files
stewSquaredbishabosha
andauthored
Add 2023 day 23 solution and input (#545)
* Port day 23 solution from stewSquared/advent-of-code * add example input for 2023 day 23 * 2023 day 17 use givens rather than globals * 2023 day 17 refactor for clarity * refactor to part1, part2 --------- Co-authored-by: Jamie Thompson <bishbashboshjt@gmail.com>
1 parent 38bc83f commit 5128b52

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

‎2023/src/day23.scala‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package day23
2+
// based on solution from https://github.com/stewSquared/adventofcode/blob/src/main/scala/2023/Day23.worksheet.sc
3+
4+
import locations.Directory.currentDir
5+
import inputs.Input.loadFileSync
6+
7+
@main def part1: Unit =
8+
println(s"The solution is ${part1(loadInput())}")
9+
10+
@main def part2: Unit =
11+
println(s"The solution is ${part2(loadInput())}")
12+
13+
def loadInput(): String = loadFileSync(s"$currentDir/../input/day23")
14+
15+
import collection.immutable.BitSet
16+
17+
def part1(input: String): Int =
18+
given maze: Maze = Maze(parseInput(input))
19+
longestDownhillHike
20+
21+
def part2(input: String): Int =
22+
given maze: Maze = Maze(parseInput(input))
23+
longestHike
24+
25+
def parseInput(fileInput: String): Vector[Vector[Char]] = Vector.from:
26+
for line <- fileInput.split("\n")
27+
yield line.toVector
28+
29+
enum Dir:
30+
case N, S, E, W
31+
32+
def turnRight = this match
33+
case Dir.N => E
34+
case Dir.E => S
35+
case Dir.S => W
36+
case Dir.W => N
37+
38+
def turnLeft = this match
39+
case Dir.N => W
40+
case Dir.W => S
41+
case Dir.S => E
42+
case Dir.E => N
43+
44+
case class Point(x: Int, y: Int):
45+
def dist(p: Point) = math.abs(x - p.x) + math.abs(y - p.y)
46+
def adjacent = List(copy(x = x + 1), copy(x = x - 1), copy(y = y + 1), copy(y = y - 1))
47+
48+
def move(dir: Dir) = dir match
49+
case Dir.N => copy(y = y - 1)
50+
case Dir.S => copy(y = y + 1)
51+
case Dir.E => copy(x = x + 1)
52+
case Dir.W => copy(x = x - 1)
53+
54+
case class Maze(grid: Vector[Vector[Char]]):
55+
56+
def apply(p: Point): Char = grid(p.y)(p.x)
57+
58+
val xRange: Range = grid.head.indices
59+
val yRange: Range = grid.indices
60+
61+
def points: Iterator[Point] = for
62+
y <- yRange.iterator
63+
x <- xRange.iterator
64+
yield Point(x, y)
65+
66+
val walkable: Set[Point] = points.filter(p => grid(p.y)(p.x) != '#').toSet
67+
val start: Point = walkable.minBy(_.y)
68+
val end: Point = walkable.maxBy(_.y)
69+
70+
val junctions: Set[Point] = walkable.filter: p =>
71+
Dir.values.map(p.move).count(walkable) > 2
72+
.toSet + start + end
73+
74+
val slopes = Map.from[Point, Dir]:
75+
points.collect:
76+
case p if apply(p) == '^' => p -> Dir.N
77+
case p if apply(p) == 'v' => p -> Dir.S
78+
case p if apply(p) == '>' => p -> Dir.E
79+
case p if apply(p) == '<' => p -> Dir.W
80+
81+
def connectedJunctions(pos: Point)(using maze: Maze) = List.from[(Point, Int)]:
82+
def walk(pos: Point, dir: Dir): Option[Point] =
83+
val p = pos.move(dir)
84+
Option.when(maze.walkable(p) && maze.slopes.get(p).forall(_ == dir))(p)
85+
86+
def search(pos: Point, facing: Dir, dist: Int): Option[(Point, Int)] =
87+
if maze.junctions.contains(pos) then Some(pos, dist) else
88+
val adjacentSearch = for
89+
nextFacing <- LazyList(facing, facing.turnRight, facing.turnLeft)
90+
nextPos <- walk(pos, nextFacing)
91+
yield search(nextPos, nextFacing, dist + 1)
92+
93+
if adjacentSearch.size == 1 then adjacentSearch.head else None
94+
95+
for
96+
d <- Dir.values
97+
p <- walk(pos, d)
98+
junction <- search(p, d, 1)
99+
yield junction
100+
101+
def longestDownhillHike(using maze: Maze): Int =
102+
def search(pos: Point, dist: Int)(using maze: Maze): Int =
103+
if pos == maze.end then dist else
104+
connectedJunctions(pos).foldLeft(0):
105+
case (max, (n, d)) => max.max(search(n, dist + d))
106+
107+
search(maze.start, 0)
108+
109+
def longestHike(using maze: Maze): Int =
110+
type Index = Int
111+
112+
val indexOf: Map[Point, Index] =
113+
maze.junctions.toList.sortBy(_.dist(maze.start)).zipWithIndex.toMap
114+
115+
val adjacent: Map[Index, List[(Index, Int)]] =
116+
maze.junctions.toList.flatMap: p1 =>
117+
connectedJunctions(p1).flatMap: (p2, d) =>
118+
val forward = indexOf(p1) -> (indexOf(p2), d)
119+
val reverse = indexOf(p2) -> (indexOf(p1), d)
120+
List(forward, reverse)
121+
.groupMap(_._1)(_._2)
122+
123+
def search(junction: Index, visited: BitSet, totalDist: Int): Int =
124+
if junction == indexOf(maze.end) then totalDist else
125+
adjacent(junction).foldLeft(0):
126+
case (longest, (nextJunct, dist)) =>
127+
if visited(nextJunct) then longest else
128+
longest.max(search(nextJunct, visited + nextJunct, totalDist + dist))
129+
130+
search(indexOf(maze.start), BitSet.empty, 0)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /