diff --git a/solutions/alessio/day21/aoc21 b/solutions/alessio/day21/aoc21 new file mode 100755 index 0000000..b52e4c1 Binary files /dev/null and b/solutions/alessio/day21/aoc21 differ diff --git a/solutions/alessio/day21/day21.go b/solutions/alessio/day21/day21.go new file mode 100644 index 0000000..fcb5d69 --- /dev/null +++ b/solutions/alessio/day21/day21.go @@ -0,0 +1,174 @@ +package main + +import ( + "fmt" + "math" + "os" + "strconv" + "strings" + "time" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +type Pos struct { + r, c int +} + +var dirSymbols map[Pos]string = map[Pos]string{ + {0, 1}: ">", + {0, -1}: "<", + {1, 0}: "v", + {-1, 0}: "^", +} + +type PosPath struct { + pos Pos + path string +} + +var numPad map[byte]Pos = map[byte]Pos{ + '7': {0, 0}, + '8': {0, 1}, + '9': {0, 2}, + '4': {1, 0}, + '5': {1, 1}, + '6': {1, 2}, + '1': {2, 0}, + '2': {2, 1}, + '3': {2, 2}, + '-': {3, 0}, + '0': {3, 1}, + 'A': {3, 2}, +} + +var dirPad map[byte]Pos = map[byte]Pos{ + '-': {0, 0}, + '^': {0, 1}, + 'A': {0, 2}, + '<': {1, 0}, + 'v': {1, 1}, + '>': {1, 2}, +} + +func getNextDirs(from Pos, to Pos) []Pos { + nextDirs := []Pos{} + if to.r > from.r { + nextDirs = append(nextDirs, Pos{1, 0}) + } + if to.r < from.r { + nextDirs = append(nextDirs, Pos{-1, 0}) + } + if to.c > from.c { + nextDirs = append(nextDirs, Pos{0, 1}) + } + if to.c < from.c { + nextDirs = append(nextDirs, Pos{0, -1}) + } + return nextDirs +} + +func getPadPaths(start byte, end byte, byteToPos map[byte]Pos) []string { + posToByte := map[Pos]byte{} + for k, v := range byteToPos { + posToByte[v] = k + } + paths := []string{} + startPosPath := PosPath{byteToPos[start], ""} + q := []PosPath{startPosPath} + for len(q) > 0 { + curr := q[0] + q = q[1:] + + if curr.pos == byteToPos[end] { + paths = append(paths, curr.path+"A") + continue + } + + for _, dir := range getNextDirs(curr.pos, byteToPos[end]) { + nextPos := Pos{curr.pos.r + dir.r, curr.pos.c + dir.c} + dirSym := dirSymbols[dir] + path := curr.path + dirSym + nextPosPath := PosPath{nextPos, path} + if posToByte[nextPos] != '-' && posToByte[nextPos] != start { + q = append(q, nextPosPath) + } + } + } + return paths +} + +type MemoKey struct { + path string + lvl int +} + +func getShortestPath(seq string, lvl int, maxLvl int, memo *map[MemoKey]int, useNumPad bool) int { + if lvl == maxLvl { + return len(seq) + } + + if val, ok := (*memo)[MemoKey{seq, lvl}]; ok { + return val + } + + start := 'A' + totMinLen := 0 + pad := dirPad + if useNumPad { + pad = numPad + } + for _, c := range seq { + paths := getPadPaths(byte(start), byte(c), pad) + min := math.MaxInt + for _, p := range paths { + l := getShortestPath(p, lvl+1, maxLvl, memo, false) + if l < min { + min = l + } + } + totMinLen += min + start = c + } + (*memo)[MemoKey{seq, lvl}] = totMinLen + return totMinLen +} + +func solve(lines []string, levels int) { + sum := 0 + memo := map[MemoKey]int{} + for _, l := range lines { + totMinLen := getShortestPath(l, -1, levels, &memo, true) + numPart, _ := strconv.Atoi(l[:len(l)-1]) + fmt.Printf("%d * %d\n", totMinLen, numPart) + sum += totMinLen * numPart + } + + fmt.Println(sum) +} + +func part1(lines []string) { + solve(lines, 2) +} + +func part2(lines []string) { + solve(lines, 25) +} + +func main() { + data, err := os.ReadFile("./input21_def.txt") + check(err) + dataStr := strings.ReplaceAll(string(data), "\r\n", "\n") + lines := strings.Split(strings.Trim(dataStr, "\n"), "\n") + + start := time.Now() + part1(lines) + fmt.Printf("part1: %s\n", time.Since(start)) + start = time.Now() + part2(lines) + fmt.Printf("part2: %s\n", time.Since(start)) +} diff --git a/solutions/alessio/day21/go.mod b/solutions/alessio/day21/go.mod new file mode 100644 index 0000000..89ebaba --- /dev/null +++ b/solutions/alessio/day21/go.mod @@ -0,0 +1,10 @@ +module aoc21 + +go 1.23 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/solutions/alessio/day21/go.sum b/solutions/alessio/day21/go.sum new file mode 100644 index 0000000..fe99d71 --- /dev/null +++ b/solutions/alessio/day21/go.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/solutions/alessio/day21/graph/graph.go b/solutions/alessio/day21/graph/graph.go new file mode 100644 index 0000000..51cb573 --- /dev/null +++ b/solutions/alessio/day21/graph/graph.go @@ -0,0 +1,197 @@ +package graph + +import ( + "fmt" + "math" + "slices" +) + +type Graph[T comparable] struct { + Nodes []Node[T] + Edges []Edge[T] + AdjList map[T][]Edge[T] +} + +type Node[T any] struct { + Val T +} + +type Edge[T any] struct { + Weight int + From T + To *Node[T] +} + +func (graph *Graph[T]) AddNode(val T) *Node[T] { + node := Node[T]{Val: val} + graph.Nodes = append(graph.Nodes, node) + return &node +} + +func (graph *Graph[T]) RemoveNode(val T) { + delete(graph.AdjList, val) + for i, lst := range graph.AdjList { + idx := -1 + for j, e := range lst { + if e.To.Val == val { + idx = j + break + } + } + if idx != -1 { + graph.AdjList[i] = slices.Concat(lst[:idx], lst[idx+1:]) + } + } +} + +func (graph *Graph[T]) AddEdge(from T, to T, weight int) *Edge[T] { + toNode := graph.GetNode(to) + if toNode == nil { + fmt.Println("Error: toNode is nil") + return nil + } + edge := Edge[T]{Weight: weight, To: toNode} + graph.Edges = append(graph.Edges, edge) + graph.AdjList[from] = append(graph.AdjList[from], edge) + return &edge +} + +func (graph *Graph[T]) AddUndirectedEdge(from T, to T, weight int) { + graph.AddEdge(from, to, weight) + graph.AddEdge(to, from, weight) +} + +func (graph *Graph[T]) HasNode(val T) bool { + return slices.ContainsFunc(graph.Nodes, func(n Node[T]) bool { return n.Val == val }) +} + +func (graph *Graph[T]) HasEdge(from T, to T) bool { + for _, e := range graph.AdjList[from] { + if e.To.Val == to { + return true + } + } + return false +} + +func (graph *Graph[T]) GetNode(val T) *Node[T] { + for _, n := range graph.Nodes { + if n.Val == val { + return &n + } + } + return nil +} + +func (graph *Graph[T]) Init(nodes int, edges int) { + graph.Nodes = make([]Node[T], 0, nodes) + graph.Edges = make([]Edge[T], 0, edges) + graph.AdjList = make(map[T][]Edge[T], nodes) +} + +func (graph *Graph[T]) ShortestPath(from T, to T) (int, map[T]bool, []T) { + start := graph.GetNode(from) + end := graph.GetNode(to) + dist := make(map[T]int, len(graph.Nodes)) + path := map[T]bool{} + orderedPath := []T{} + nodes := make([]T, 0, len(graph.Nodes)) + for _, n := range graph.Nodes { + dist[n.Val] = math.MaxInt + nodes = append(nodes, n.Val) + } + + dist[start.Val] = 0 + for len(path) < len(nodes) { + curr := graph.getClosest(nodes, dist, path) + if path[curr] { + continue + } + + path[curr] = true + orderedPath = append(orderedPath, curr) + + if curr == end.Val { + break + } + + for _, e := range graph.AdjList[curr] { + // relaxation on the edge + if !path[e.To.Val] && dist[curr]+e.Weight < dist[e.To.Val] { + dist[e.To.Val] = dist[curr] + e.Weight + } + } + } + return dist[end.Val], path, orderedPath +} + +func (graph *Graph[T]) GetOnlyPath(from T, to T) (map[T]bool, []T) { + start := graph.GetNode(from) + end := graph.GetNode(to) + path := map[T]bool{} + orderedPath := []T{} + + curr := start.Val + for curr != end.Val { + path[curr] = true + orderedPath = append(orderedPath, curr) + numAdj := len(graph.AdjList[curr]) + if numAdj > 2 { + panic("too many adjacents") + } + for _, e := range graph.AdjList[curr] { + if !path[e.To.Val] { + curr = e.To.Val + break + } + } + } + path[curr] = true + orderedPath = append(orderedPath, curr) + return path, orderedPath +} + +func (graph *Graph[T]) getClosest(nodes []T, dist map[T]int, visited map[T]bool) T { + var minNode T + minDist := math.MaxInt + + for _, n := range nodes { + if !visited[n] && dist[n] <= minDist { + minDist = dist[n] + minNode = n + } + } + return minNode +} + +func (graph *Graph[T]) GetFullPathsEdges(start T, end T) []Edge[T] { + allEdges := []Edge[T]{} + min := math.MaxInt + graph.getBestPathsRec(start, &map[T]bool{}, []Edge[T]{}, &allEdges, end, 0, &min) + return allEdges +} + +func (graph *Graph[T]) getBestPathsRec(currNode T, visited *map[T]bool, currPath []Edge[T], edges *[]Edge[T], end T, currVal int, currMin *int) { + if currNode == end { + fmt.Println("found path") + if currVal < *currMin { + *currMin = currVal + (*edges) = append([]Edge[T]{}, currPath...) // replace all + } else if currVal == *currMin { + (*edges) = append((*edges), currPath...) + } + return + } + + (*visited)[currNode] = true + for _, e := range graph.AdjList[currNode] { + next := e.To.Val + if !(*visited)[next] { + e.From = currNode + if currVal+e.Weight <= *currMin { + graph.getBestPathsRec(next, visited, append(currPath, e), edges, end, currVal+e.Weight, currMin) + } + } + } + (*visited)[currNode] = false +} diff --git a/solutions/alessio/day21/input21.txt b/solutions/alessio/day21/input21.txt new file mode 100644 index 0000000..dd73dfd --- /dev/null +++ b/solutions/alessio/day21/input21.txt @@ -0,0 +1,5 @@ +029A +980A +179A +456A +379A \ No newline at end of file diff --git a/solutions/alessio/day21/input21_def.txt b/solutions/alessio/day21/input21_def.txt new file mode 100644 index 0000000..a3e5181 --- /dev/null +++ b/solutions/alessio/day21/input21_def.txt @@ -0,0 +1,5 @@ +463A +340A +129A +083A +341A \ No newline at end of file