Skip to content

Commit

Permalink
day 21 solution
Browse files Browse the repository at this point in the history
  • Loading branch information
aleche28 committed Dec 23, 2024
1 parent 33e0e67 commit 13be453
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 0 deletions.
Binary file added solutions/alessio/day21/aoc21
Binary file not shown.
174 changes: 174 additions & 0 deletions solutions/alessio/day21/day21.go
Original file line number Diff line number Diff line change
@@ -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))
}
10 changes: 10 additions & 0 deletions solutions/alessio/day21/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
9 changes: 9 additions & 0 deletions solutions/alessio/day21/go.sum
Original file line number Diff line number Diff line change
@@ -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=
197 changes: 197 additions & 0 deletions solutions/alessio/day21/graph/graph.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 13be453

Please sign in to comment.