Skip to content
This repository was archived by the owner on Jan 5, 2025. It is now read-only.

Commit 64185f1

Browse files
Solved day 21
1 parent f62c35b commit 64185f1

File tree

5 files changed

+104
-1
lines changed

5 files changed

+104
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ _My solutions to the 2024 edition of [Advent of Code](https://adventofcode.com/2
3636
| **[18](https://adventofcode.com/2024/day/18)** | [solution](src/main/scala/adventofcode/solutions/Day18.scala) |
3737
| **[19](https://adventofcode.com/2024/day/19)** | [solution](src/main/scala/adventofcode/solutions/Day19.scala) |
3838
| **[20](https://adventofcode.com/2024/day/20)** | [solution](src/main/scala/adventofcode/solutions/Day20.scala) |
39-
| **[21](https://adventofcode.com/2024/day/21)** | [](src/main/scala/adventofcode/solutions/Day21.scala) |
39+
| **[21](https://adventofcode.com/2024/day/21)** | [solution](src/main/scala/adventofcode/solutions/Day21.scala) |
4040
| **[22](https://adventofcode.com/2024/day/22)** | [](src/main/scala/adventofcode/solutions/Day22.scala) |
4141
| **[23](https://adventofcode.com/2024/day/23)** | [](src/main/scala/adventofcode/solutions/Day23.scala) |
4242
| **[24](https://adventofcode.com/2024/day/24)** | [](src/main/scala/adventofcode/solutions/Day24.scala) |

input/21.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
149A
2+
582A
3+
540A
4+
246A
5+
805A

output/21-1.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
164960

output/21-2.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
205620604017764
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package adventofcode.solutions
2+
3+
import adventofcode.Definitions.*
4+
5+
@main def Day21 = Day(21) { (input, part) =>
6+
7+
case class Vec(i: Int, j: Int):
8+
infix def +(that: Vec): Vec = Vec(i + that.i, j + that.j)
9+
infix def -(that: Vec): Vec = Vec(i - that.i, j - that.j)
10+
def manhattan: Int = i.abs + j.abs
11+
12+
def parseLayout(s: String): Map[Char, Vec] =
13+
s.split(lineSeparator).zipWithIndex.flatMap((row, i) => row.zipWithIndex.collect { case (c, j) if c != ' ' => c -> Vec(i, j) }).toMap
14+
15+
val layout1 = parseLayout(
16+
"""789
17+
|456
18+
|123
19+
| 0A
20+
|""".stripMargin
21+
)
22+
23+
val layout2 = parseLayout(
24+
""" ^A
25+
|<v>
26+
|""".stripMargin
27+
)
28+
29+
val directions = Map(
30+
'^' -> Vec(-1, 0),
31+
'<' -> Vec(0, -1),
32+
'v' -> Vec(1, 0),
33+
'>' -> Vec(0, 1)
34+
)
35+
val directionsInverse = directions.toSeq.map(_.swap).toMap
36+
37+
val A = 'A'
38+
39+
def isValidPath(position: Vec, path: Seq[Vec], validPositions: Set[Vec]): Boolean =
40+
if validPositions.contains(position) then
41+
path match
42+
case head +: tail => isValidPath(position + head, tail, validPositions)
43+
case _ => true
44+
else
45+
false
46+
47+
def optimalPaths(from: Vec, to: Vec): Set[Seq[Vec]] =
48+
val d = to - from
49+
val path = Seq(Vec(d.i, 0), Vec(0, d.j)).map(v => Vec(v.i.sign, v.j.sign) -> (v.i.abs + v.j.abs)).filter((_, d) => d != 0)
50+
Seq(path, path.reverse).toSet.map(_.flatMap((vec, count) => Seq.fill(count)(vec)))
51+
52+
def buildLevelMap(layout: Map[Char, Vec]): Map[(Char, Char), Set[Seq[Vec]]] =
53+
val validPositions = layout.values.toSet
54+
(for
55+
a <- layout.keys
56+
b <- layout.keys
57+
(pa, pb) = (layout(a), layout(b))
58+
paths = optimalPaths(pa, pb).filter(isValidPath(pa, _, validPositions))
59+
yield (a, b) -> paths).toMap
60+
61+
val (digitsLevelMap, arrowsLevelMap) = (buildLevelMap(layout1), buildLevelMap(layout2))
62+
63+
type Cache = Map[(Char, Char, Int), Long]
64+
65+
def count(level: Map[(Char, Char), Set[Seq[Vec]]], start: Char, end: Char, depth: Int, cache: Cache): (Long, Cache) =
66+
val cacheKey = (start, end, depth)
67+
cache.get(cacheKey) match
68+
case Some(value) => (value, cache)
69+
case _ =>
70+
if depth >= 0 then
71+
val paths = level((start, end)).map(_.map(directionsInverse) :+ A)
72+
val (values, cache3) = paths.foldLeft((Seq.empty[Long], cache)) { case ((costs, cache1), path) =>
73+
val (c, cache2) = countAll(arrowsLevelMap, path, depth - 1, cache1)
74+
(costs :+ c, cache2)
75+
}
76+
val value = values.min
77+
(value, cache3 + (cacheKey -> value))
78+
else
79+
(1, cache)
80+
81+
def countAll(level: Map[(Char, Char), Set[Seq[Vec]]], keys: Seq[Char], depth: Int, cache: Cache): (Long, Cache) =
82+
(A +: keys)
83+
.sliding(2).collect { case Seq(start, end) => (start, end) }
84+
.foldLeft((0L, cache)) { case ((sum, cache1), (start, end)) =>
85+
val (result, cache2) = count(level, start, end, depth, cache1)
86+
(sum + result, cache2)
87+
}
88+
89+
def solve(robots: Int): Long =
90+
input.toLines.map(code => countAll(digitsLevelMap, code, robots, Map.empty)._1 * code.init.mkString.toInt).sum
91+
92+
part(1) = solve(2)
93+
94+
part(2) = solve(25)
95+
96+
}

0 commit comments

Comments
 (0)