Skip to content

Commit 5fe5eb5

Browse files
committed
feat(2024): add day 20 code
1 parent 56f90ad commit 5fe5eb5

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

2024/src/day20.scala

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package day20
2+
3+
import scala.annotation.tailrec
4+
import scala.collection.immutable.Range.Inclusive
5+
6+
import locations.Directory.currentDir
7+
import inputs.Input.loadFileSync
8+
9+
@main def part1: Unit =
10+
println(s"The solution is ${part1(loadInput())}")
11+
12+
@main def part2: Unit =
13+
println(s"The solution is ${part2(loadInput())}")
14+
15+
def loadInput(): String = loadFileSync(s"$currentDir/../input/day20")
16+
17+
extension (x: Int) inline def ±(y: Int) = x - y to x + y
18+
extension (x: Inclusive)
19+
inline def &(y: Inclusive) = (x.start max y.start) to (x.end min y.end)
20+
21+
opaque type Pos = (Int, Int)
22+
23+
object Pos:
24+
val up: Pos = (0, -1)
25+
val down: Pos = (0, 1)
26+
val left: Pos = (-1, 0)
27+
val right: Pos = (1, 0)
28+
val zero: Pos = (0, 0)
29+
def apply(x: Int, y: Int): Pos = (x, y)
30+
31+
extension (p: Pos)
32+
inline def x = p._1
33+
inline def y = p._2
34+
inline def neighbors: List[Pos] =
35+
List(p + up, p + right, p + down, p + left)
36+
inline def +(q: Pos): Pos = (p.x + q.x, p.y + q.y)
37+
inline infix def taxiDist(q: Pos) = (p.x - q.x).abs + (p.y - q.y).abs
38+
39+
case class Rect(x: Inclusive, y: Inclusive):
40+
inline def &(that: Rect) = Rect(x & that.x, y & that.y)
41+
42+
def iterator: Iterator[Pos] = for
43+
y <- y.iterator
44+
x <- x.iterator
45+
yield Pos(x, y)
46+
47+
object Track:
48+
def parse(input: String) =
49+
val lines = input.trim.split('\n')
50+
val bounds = Rect(0 to lines.head.size - 1, 0 to lines.size - 1)
51+
val track = Track(Pos.zero, Pos.zero, Set.empty, bounds)
52+
bounds.iterator.foldLeft(track) { (track, p) =>
53+
lines(p.y)(p.x) match
54+
case 'S' => track.copy(start = p)
55+
case 'E' => track.copy(end = p)
56+
case '#' => track.copy(walls = track.walls + p)
57+
case _ => track
58+
}
59+
60+
case class Track(start: Pos, end: Pos, walls: Set[Pos], bounds: Rect):
61+
lazy val path: Vector[Pos] =
62+
inline def canMove(prev: List[Pos])(p: Pos) =
63+
!walls.contains(p) && Some(p) != prev.headOption
64+
65+
@tailrec def go(xs: List[Pos]): List[Pos] = xs match
66+
case Nil => Nil
67+
case p :: _ if p == end => xs
68+
case p :: ys => go(p.neighbors.filter(canMove(ys)) ++ xs)
69+
70+
go(List(start)).reverseIterator.toVector
71+
72+
lazy val zipped = path.zipWithIndex
73+
lazy val pathMap = zipped.toMap
74+
75+
def cheatedPaths(maxDist: Int) =
76+
def radius(p: Pos) =
77+
(Rect(p.x ± maxDist, p.y ± maxDist) & bounds).iterator
78+
.filter(p.taxiDist(_) <= maxDist)
79+
80+
zipped.map { (p, i) =>
81+
radius(p)
82+
.flatMap(pathMap.get)
83+
.map { j => (j - i) - (p taxiDist path(j)) }
84+
.count(_ >= 100)
85+
}.sum
86+
87+
def part1(input: String): Int = Track.parse(input).cheatedPaths(2)
88+
def part2(input: String): Int = Track.parse(input).cheatedPaths(20)

0 commit comments

Comments
 (0)