From 345151abb86f108cb62dc4937528cb272b2e8a25 Mon Sep 17 00:00:00 2001 From: Chris Lewis Date: Sat, 21 Dec 2024 21:52:34 +1100 Subject: [PATCH] day 21 --- 21/input_21 | 5 ++ 21/twentyone.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++ util/grid/grid.go | 36 ++++++++++- 3 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 21/input_21 create mode 100644 21/twentyone.go diff --git a/21/input_21 b/21/input_21 new file mode 100644 index 0000000..5127a5e --- /dev/null +++ b/21/input_21 @@ -0,0 +1,5 @@ +208A +586A +341A +463A +593A \ No newline at end of file diff --git a/21/twentyone.go b/21/twentyone.go new file mode 100644 index 0000000..83f2ea4 --- /dev/null +++ b/21/twentyone.go @@ -0,0 +1,149 @@ +package main + +import ( + "fmt" + "math" + "slices" + "strconv" + "strings" + + "github.com/cdlewis/advent-of-code/util/aoc" + "github.com/cdlewis/advent-of-code/util/grid" +) + +var testInput = `029A +980A +179A +456A +379A` + +var keypad = grid.Grid[byte]([][]byte{ + {'7', '8', '9'}, + {'4', '5', '6'}, + {'1', '2', '3'}, + {'#', '0', 'A'}, +}) + +var directionPad = [][]byte{ + {'#', '^', 'A'}, + {'<', 'v', '>'}, +} + +var changeToDirection = map[grid.Point]byte{ + grid.UP: '^', + grid.DOWN: 'v', + grid.LEFT: '<', + grid.RIGHT: '>', +} + +func main() { + input := strings.Split(aoc.GetInput(21, false, testInput), "\n") + + total := 0 + for _, row := range input { + key, _ := strconv.Atoi(row[:len(row)-1]) + complexity := cost(row, -1) + total += (key * complexity) + } + + fmt.Println(total == 195664513288128) +} + +var cache = map[string]int{} + +func cost(code string, robot int) int { + if result, ok := cache[code+strconv.Itoa(robot)]; ok { + return result + } + + maze := keypad + if robot > 0 { + maze = directionPad + } + + totalCost := 0 + prev := 'A' + for _, curr := range code { + posA, _ := maze.Find(byte(prev)) + posB, _ := maze.Find(byte(curr)) + + paths := allPaths(posA, posB, maze) + cheapestPath := math.MaxInt + for _, p := range paths { + if robot == -1 { + length := cost(p, 25) + if length < cheapestPath { + cheapestPath = length + } + } else if robot > 1 { + length := cost(p, robot-1) + if length < cheapestPath { + cheapestPath = length + } + } else { + if len(p) < cheapestPath { + cheapestPath = len(p) + } + } + } + + totalCost += cheapestPath + prev = curr + } + + cache[code+strconv.Itoa(robot)] = totalCost + return totalCost +} + +type item struct { + path []grid.Point + seen []grid.Point +} + +func allPaths( + a grid.Point, + b grid.Point, + maze grid.Grid[byte], +) []string { + var results [][]grid.Point + q := []item{{seen: []grid.Point{a}}} + + for len(q) > 0 { + curr := q[0] + currHead := curr.seen[len(curr.seen)-1] + q = q[1:] + + if currHead == b { + results = append(results, curr.path) + } + + for _, d := range grid.Directions { + nextPos := currHead.Add(d) + if maze.GetOrElse(nextPos, '#') == '#' { + continue + } + + if slices.Index(curr.seen, nextPos) != -1 { + continue + } + + q = append(q, item{ + path: append(slices.Clone(curr.path), d), + seen: append(slices.Clone(curr.seen), nextPos), + }) + } + } + + var adaptedResults []string + for _, r := range results { + var adaptedResult []byte + for _, i := range r { + adaptedResult = append(adaptedResult, changeToDirection[i]) + } + adaptedResult = append(adaptedResult, 'A') + + adaptedResults = append(adaptedResults, string(adaptedResult)) + } + + return adaptedResults +} diff --git a/util/grid/grid.go b/util/grid/grid.go index d4a4044..9ca8c28 100644 --- a/util/grid/grid.go +++ b/util/grid/grid.go @@ -18,6 +18,24 @@ func (p Point) Subtract(another Point) Point { return SubtractPoints(p, another) } +func (p Point) ToDirection() []Point { + var results []Point + + if p[0] > 0 { + results = append(results, DOWN) + } else if p[0] < 0 { + results = append(results, UP) + } + + if p[1] > 0 { + results = append(results, RIGHT) + } else if p[1] < 0 { + results = append(results, LEFT) + } + + return results +} + func (p Point) RotateClockwise() Point { switch p { case LEFT: @@ -48,7 +66,7 @@ func (p Point) RotateCounterClockwise() Point { panic("impossible state") } -type Grid[T any] [][]T +type Grid[T comparable] [][]T func (g Grid[T]) ValidPoint(point Point) bool { return ValidPointCoordinate(point, g) @@ -81,6 +99,18 @@ func (g Grid[T]) GetAdjacent(point Point) []Point { return result } +func (g Grid[T]) Find(needle T) (Point, bool) { + for idx := range g { + for jdx := range g[idx] { + if g[idx][jdx] == needle { + return Point{idx, jdx}, true + } + } + } + + return Point{}, false +} + func (g Grid[T]) Print() { for _, i := range g { for _, j := range i { @@ -108,7 +138,7 @@ var DOWN = Point{1, 0} var LEFT = Point{0, -1} var RIGHT = Point{0, 1} -var Directions = [][2]int{UP, DOWN, LEFT, RIGHT} +var Directions = []Point{UP, DOWN, LEFT, RIGHT} var DirectionsDiagonal = [][2]int{ {-1, -1}, {-1, 0}, {-1, 1}, @@ -132,7 +162,7 @@ var Directions3D = [][]int{ {0, 0, -1}, } -func ShortestUnweightedPath[U any]( +func ShortestUnweightedPath[U comparable]( graph Grid[U], start Point, isEnd func(Point) bool,