Files
adventofcode/2024/go/day21/day21.go
2025-02-14 15:14:46 +00:00

182 lines
4.0 KiB
Go

package day21
import (
"adventofcode2024/utils"
"adventofcode2024/utils/dijkstra"
"adventofcode2024/utils/memo"
"fmt"
"math"
"strings"
)
type Pair struct {
s dijkstra.Point
d dijkstra.Point
}
type Path struct {
path []dijkstra.Point
path_str string
cost int
}
type State struct {
code string
depth int
isNumericPad bool
}
var numericPad = map[dijkstra.Point]string{
{X: 0, Y: 0}: "7", {X: 1, Y: 0}: "8", {X: 2, Y: 0}: "9",
{X: 0, Y: 1}: "4", {X: 1, Y: 1}: "5", {X: 2, Y: 1}: "6",
{X: 0, Y: 2}: "1", {X: 1, Y: 2}: "2", {X: 2, Y: 2}: "3",
{X: 1, Y: 3}: "0", {X: 2, Y: 3}: "A",
}
var directionPad = map[dijkstra.Point]string{
{X: 1, Y: 0}: "^", {X: 2, Y: 0}: "A",
{X: 0, Y: 1}: "<", {X: 1, Y: 1}: "v", {X: 2, Y: 1}: ">",
}
var directions = []dijkstra.Point{{X: 0, Y: 1}, {X: 0, Y: -1}, {X: 1, Y: 0}, {X: -1, Y: 0}}
var lookup_np = get_paths(numericPad)
var lookup_dp = get_paths(directionPad)
var m *memo.Memo
func init() {
m = memo.New(get_shortest_path)
}
func Part1(input string) int {
codes := strings.Split(input, "\n")
cost := 0
for _, code := range codes {
initialState := State{
code: code,
depth: 2,
isNumericPad: true,
}
result := m.Get(initialState).(int)
cost += result * utils.MustAtoi(code[:len(code)-1])
}
return cost
}
func Part2(input string) int {
codes := strings.Split(input, "\n")
cost := 0
for _, code := range codes {
initialState := State{
code: code,
depth: 25,
isNumericPad: true,
}
result := m.Get(initialState).(int)
cost += result * utils.MustAtoi(code[:len(code)-1])
}
return cost
}
func get_paths(in map[dijkstra.Point]string) map[Pair][]Path {
lookup := make(map[Pair][]Path)
data := dijkstra.InputGraph{}
for p := range in {
for _, dir := range directions {
np := dijkstra.Point{X: p.X + dir.X, Y: p.Y + dir.Y}
if in[np] != "" {
data.Graph = append(data.Graph, dijkstra.InputData{
Source: p,
Destination: np,
Weight: 1,
})
}
}
}
graph := dijkstra.CreateGraph(data)
for s := range in {
for d := range in {
paths, cost := dijkstra.GetAllShortestPaths(s, d, graph)
for _, path := range paths {
path_str := ""
for x := 1; x < len(path); x++ {
f := path[x-1]
t := path[x]
switch {
case f.X == t.X && f.Y < t.Y:
// Moving verticallY down
path_str += "v"
case f.X == t.X && f.Y > t.Y:
// Moving verticallY up
path_str += "^"
case f.Y == t.Y && f.X < t.X:
// Moving horizontallY right
path_str += ">"
case f.Y == t.Y && f.X > t.X:
// Moving horizontally left
path_str += "<"
default:
// No matching case or invalid input
fmt.Println("Invalid direction")
}
}
lookup[Pair{s, d}] = append(lookup[Pair{s, d}], Path{path, path_str, cost})
}
}
}
return lookup
}
func find_point(in string, lookup map[dijkstra.Point]string) dijkstra.Point {
for k, v := range lookup {
if v == in {
return k
}
}
return dijkstra.Point{}
}
func get_shortest_path(key interface{}) interface{} {
state := key.(State)
if state.depth < 0 {
return len(state.code)
}
// (Optional) If you need to modify the state, work on a copy.
newState := state
newState.code = "A" + state.code
cost := 0
for x := 0; x<len(newState.code)-1; x++ {
pps := shortest_paths(string(newState.code[x]), string(newState.code[x+1]), newState.isNumericPad)
min := math.MaxInt // Start with a very high number not just 65535
for _, pp := range pps {
nextState := State{
code: pp.path_str + "A",
depth: newState.depth - 1,
isNumericPad: false,
}
mm := m.Get(nextState).(int)
if mm < min {
min = mm
}
}
cost += min
}
return cost
}
func shortest_paths(s, d string, isNumericPad bool) []Path {
if isNumericPad {
return lookup_np[Pair{s: find_point(s, numericPad), d: find_point(d, numericPad)}]
} else {
return lookup_dp[Pair{s: find_point(s, directionPad), d: find_point(d, directionPad)}]
}
}