|
| 1 | +package eu.sim642.adventofcode2024 |
| 2 | + |
| 3 | +import eu.sim642.adventofcodelib.Grid |
| 4 | +import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, Heuristic, UnitNeighbors} |
| 5 | +import eu.sim642.adventofcodelib.pos.Pos |
| 6 | +import eu.sim642.adventofcodelib.GridImplicits.* |
| 7 | + |
| 8 | +object Day21 { |
| 9 | + |
| 10 | + type Code = String |
| 11 | + |
| 12 | + private val numericKeypad: Grid[Char] = Vector( |
| 13 | + Vector('7', '8', '9'), |
| 14 | + Vector('4', '5', '6'), |
| 15 | + Vector('1', '2', '3'), |
| 16 | + Vector(' ', '0', 'A'), |
| 17 | + ) |
| 18 | + |
| 19 | + private val directionalKeypad: Grid[Char] = Vector( |
| 20 | + Vector(' ', '^', 'A'), |
| 21 | + Vector('<', 'v', '>'), |
| 22 | + ) |
| 23 | + |
| 24 | + private val directionalOffsets = Map( |
| 25 | + '^' -> Pos(0, -1), |
| 26 | + '>' -> Pos(1, 0), |
| 27 | + 'v' -> Pos(0, 1), |
| 28 | + '<' -> Pos(-1, 0), |
| 29 | + ) |
| 30 | + |
| 31 | + case class State(directionalPoss: List[Pos], numericPos: Pos, input: Code) { |
| 32 | + |
| 33 | + def numericPress(button: Char): Option[State] = button match { |
| 34 | + case 'A' => |
| 35 | + val newButton = numericKeypad(numericPos) |
| 36 | + Some(copy(input = input + newButton)) |
| 37 | + case _ => |
| 38 | + val offset = directionalOffsets(button) |
| 39 | + val newNumericPos = numericPos + offset |
| 40 | + if (numericKeypad.containsPos(newNumericPos) && numericKeypad(newNumericPos) != ' ') |
| 41 | + Some(copy(numericPos = newNumericPos)) |
| 42 | + else |
| 43 | + None // out of keypad |
| 44 | + } |
| 45 | + |
| 46 | + def directionalPress(button: Char): Option[State] = directionalPoss match { |
| 47 | + case Nil => numericPress(button) |
| 48 | + case directionalPos :: newDirectionalPoss => |
| 49 | + button match { |
| 50 | + case 'A' => |
| 51 | + val newButton = directionalKeypad(directionalPos) |
| 52 | + copy(directionalPoss = newDirectionalPoss).directionalPress(newButton).map(newState => |
| 53 | + newState.copy(directionalPoss = directionalPos :: newState.directionalPoss) |
| 54 | + ) |
| 55 | + case _ => |
| 56 | + val offset = directionalOffsets(button) |
| 57 | + val newDirectionalPos = directionalPos + offset |
| 58 | + if (directionalKeypad.containsPos(newDirectionalPos) && directionalKeypad(newDirectionalPos) != ' ') |
| 59 | + Some(copy(directionalPoss = newDirectionalPos :: newDirectionalPoss)) |
| 60 | + else |
| 61 | + None // out of keypad |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + def userPress(button: Char): Option[State] = directionalPress(button) |
| 66 | + } |
| 67 | + |
| 68 | + def shortestSequenceLength(code: Code): Int = { |
| 69 | + |
| 70 | + val graphSearch = new GraphSearch[State] with UnitNeighbors[State] { |
| 71 | + override val startNode: State = State(List.fill(2)(directionalKeypad.posOf('A')), numericKeypad.posOf('A'), "") |
| 72 | + |
| 73 | + override def unitNeighbors(state: State): IterableOnce[State] = "<v>^A".iterator.flatten(state.userPress).filter(s => code.startsWith(s.input)) |
| 74 | + |
| 75 | + override def isTargetNode(state: State, dist: Int): Boolean = state.input == code |
| 76 | + } |
| 77 | + |
| 78 | + BFS.search(graphSearch).target.get._2 |
| 79 | + } |
| 80 | + |
| 81 | + def codeComplexity(code: Code): Int = { |
| 82 | + val numericPart = code.dropRight(1).toInt |
| 83 | + shortestSequenceLength(code) * numericPart |
| 84 | + } |
| 85 | + |
| 86 | + def sumCodeComplexity(codes: Seq[Code]): Int = codes.map(codeComplexity).sum |
| 87 | + |
| 88 | + def parseCodes(input: String): Seq[Code] = input.linesIterator.toSeq |
| 89 | + |
| 90 | + lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day21.txt")).mkString.trim |
| 91 | + |
| 92 | + def main(args: Array[String]): Unit = { |
| 93 | + println(sumCodeComplexity(parseCodes(input))) |
| 94 | + } |
| 95 | +} |
0 commit comments