|
1 | 1 | package eu.sim642.adventofcode2024
|
2 | 2 |
|
3 | 3 | import eu.sim642.adventofcodelib.Grid
|
4 |
| -import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, Heuristic, UnitNeighbors} |
| 4 | +import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, GraphTraversal, Heuristic, SimultaneousBFS, TargetNode, UnitNeighbors} |
5 | 5 | import eu.sim642.adventofcodelib.pos.Pos
|
6 | 6 | import eu.sim642.adventofcodelib.GridImplicits.*
|
| 7 | +import eu.sim642.adventofcodelib.box.Box |
| 8 | + |
| 9 | +import scala.collection.mutable |
7 | 10 |
|
8 | 11 | object Day21 {
|
9 | 12 |
|
@@ -78,18 +81,96 @@ object Day21 {
|
78 | 81 | BFS.search(graphSearch).target.get._2
|
79 | 82 | }
|
80 | 83 |
|
81 |
| - def codeComplexity(code: Code): Int = { |
| 84 | + |
| 85 | + // copied & modified from 2024 day 10 |
| 86 | + // TODO: extract to library? |
| 87 | + def pathSearch[A](graphSearch: GraphSearch[A] & UnitNeighbors[A]): GraphSearch[List[A]] & UnitNeighbors[List[A]] = { |
| 88 | + new GraphSearch[List[A]] with UnitNeighbors[List[A]] { |
| 89 | + override val startNode: List[A] = List(graphSearch.startNode) |
| 90 | + |
| 91 | + override def unitNeighbors(node: List[A]): IterableOnce[List[A]] = |
| 92 | + graphSearch.unitNeighbors(node.head).iterator.map(_ :: node) |
| 93 | + |
| 94 | + override def isTargetNode(node: List[A], dist: Int): Boolean = graphSearch.isTargetNode(node.head, dist) |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + private def keypadPaths(keypad: Grid[Char]): Map[(Char, Char), Set[Code]] = { |
| 99 | + val box = Box(Pos.zero, Pos(keypad(0).size - 1, keypad.size - 1)) |
| 100 | + (for { |
| 101 | + startPos <- box.iterator |
| 102 | + if keypad(startPos) != ' ' |
| 103 | + targetPos <- box.iterator |
| 104 | + if keypad(targetPos) != ' ' |
| 105 | + } yield { |
| 106 | + val graphSearch = new GraphSearch[Pos] with UnitNeighbors[Pos] with TargetNode[Pos] { |
| 107 | + override val startNode: Pos = startPos |
| 108 | + |
| 109 | + override def unitNeighbors(pos: Pos): IterableOnce[Pos] = |
| 110 | + Pos.axisOffsets.map(pos + _).filter(keypad.containsPos).filter(keypad(_) != ' ') |
| 111 | + |
| 112 | + override val targetNode: Pos = targetPos |
| 113 | + } |
| 114 | + (keypad(targetPos), keypad(startPos)) -> // flipped because paths are reversed |
| 115 | + SimultaneousBFS.search(pathSearch(graphSearch)) |
| 116 | + .nodes |
| 117 | + .filter(_.head == targetPos) |
| 118 | + .map(poss => |
| 119 | + (poss lazyZip poss.tail) |
| 120 | + .map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 }) |
| 121 | + .mkString |
| 122 | + ) |
| 123 | + .toSet |
| 124 | + }).toMap |
| 125 | + } |
| 126 | + |
| 127 | + private val numericPaths: Map[(Char, Char), Set[Code]] = keypadPaths(numericKeypad) |
| 128 | + private val directionalPaths: Map[(Char, Char), Set[Code]] = keypadPaths(directionalKeypad) |
| 129 | + |
| 130 | + //println(numericPaths) |
| 131 | + |
| 132 | + def shortestSequenceLength2(code: Code, directionalKeypads: Int, i: Int = 0): Long = { |
| 133 | + |
| 134 | + val memo = mutable.Map.empty[(Code, Int), Long] |
| 135 | + |
| 136 | + def helper(code: Code, i: Int): Long = { |
| 137 | + memo.getOrElseUpdate((code, i), { |
| 138 | + //assert(directionalKeypads == 0) |
| 139 | + code.foldLeft(('A', 0L))({ case ((prev, length), cur) => |
| 140 | + val newLength = |
| 141 | + (for { |
| 142 | + path <- if (i == 0) numericPaths((prev, cur)) else directionalPaths((prev, cur)) |
| 143 | + path2 = path + 'A' |
| 144 | + len = |
| 145 | + if (i == directionalKeypads) |
| 146 | + path2.length.toLong |
| 147 | + else |
| 148 | + helper(path2, i + 1) |
| 149 | + } yield len).min |
| 150 | + (cur, length + newLength) |
| 151 | + })._2 |
| 152 | + }) |
| 153 | + } |
| 154 | + |
| 155 | + helper(code, 0) |
| 156 | + } |
| 157 | + |
| 158 | + |
| 159 | + def codeComplexity(code: Code, directionalKeypads: Int): Long = { |
82 | 160 | val numericPart = code.dropRight(1).toInt
|
83 |
| - shortestSequenceLength(code) * numericPart |
| 161 | + shortestSequenceLength2(code, directionalKeypads) * numericPart |
84 | 162 | }
|
85 | 163 |
|
86 |
| - def sumCodeComplexity(codes: Seq[Code]): Int = codes.map(codeComplexity).sum |
| 164 | + def sumCodeComplexity(codes: Seq[Code], directionalKeypads: Int): Long = codes.map(codeComplexity(_, directionalKeypads)).sum |
87 | 165 |
|
88 | 166 | def parseCodes(input: String): Seq[Code] = input.linesIterator.toSeq
|
89 | 167 |
|
90 | 168 | lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day21.txt")).mkString.trim
|
91 | 169 |
|
92 | 170 | def main(args: Array[String]): Unit = {
|
93 |
| - println(sumCodeComplexity(parseCodes(input))) |
| 171 | + println(sumCodeComplexity(parseCodes(input), 2)) |
| 172 | + println(sumCodeComplexity(parseCodes(input), 25)) |
| 173 | + |
| 174 | + // part 2: 1301407762 - too low (Int overflowed in shortestSequenceLength2) |
94 | 175 | }
|
95 | 176 | }
|
0 commit comments