Added code for day 21

This commit is contained in:
Ceferino Patino 2024-12-21 18:15:39 -06:00
commit 0023a94368
Signed by: c4patino
SSH key fingerprint: SHA256:9fQ9TsujGrdNNi76mnsu63v7dS5JOmHRZEqBOl49OR8
5 changed files with 305 additions and 0 deletions

View file

@ -29,6 +29,7 @@ import (
"cpatino.com/advent-of-code/2024/day18"
"cpatino.com/advent-of-code/2024/day19"
"cpatino.com/advent-of-code/2024/day20"
"cpatino.com/advent-of-code/2024/day21"
)
var days = map[int]func(string) (interface{}, interface{}){
@ -52,6 +53,7 @@ var days = map[int]func(string) (interface{}, interface{}){
18: day18.Run,
19: day19.Run,
20: day20.Run,
21: day21.Run,
}
var rootCmd = &cobra.Command{

259
2024/day21/day21.go Executable file
View file

@ -0,0 +1,259 @@
package day21
import (
"bufio"
"image"
"math"
"os"
"regexp"
"strconv"
)
type Keypad interface {
GetButtonPosition(button rune) image.Point
}
type NumericKeypad struct {
ButtonPositions map[rune]image.Point
}
func (k *NumericKeypad) GetButtonPosition(button rune) image.Point {
return k.ButtonPositions[button]
}
type DirectionalKeypad struct {
ButtonPositions map[rune]image.Point
}
func (k *DirectionalKeypad) GetButtonPosition(button rune) image.Point {
return k.ButtonPositions[button]
}
var numeric = NumericKeypad{
ButtonPositions: map[rune]image.Point{
'A': image.Point{2, 0},
'0': image.Point{1, 0},
'1': image.Point{0, 1},
'2': image.Point{1, 1},
'3': image.Point{2, 1},
'4': image.Point{0, 2},
'5': image.Point{1, 2},
'6': image.Point{2, 2},
'7': image.Point{0, 3},
'8': image.Point{1, 3},
'9': image.Point{2, 3},
},
}
var directional = DirectionalKeypad{
ButtonPositions: map[rune]image.Point{
'A': image.Point{2, 1},
'^': image.Point{1, 1},
'<': image.Point{0, 0},
'v': image.Point{1, 0},
'>': image.Point{2, 0},
},
}
func getDirectional(input string, start rune) string {
curr := directional.GetButtonPosition(start)
seq := []rune{}
for _, char := range input {
dest := directional.GetButtonPosition(char)
dx, dy := dest.X-curr.X, dest.Y-curr.Y
horiz, vert := []rune{}, []rune{}
for i := 0; i < int(math.Abs(float64(dx))); i++ {
if dx >= 0 {
horiz = append(horiz, '>')
} else {
horiz = append(horiz, '<')
}
}
for i := 0; i < int(math.Abs(float64(dy))); i++ {
if dy >= 0 {
vert = append(vert, '^')
} else {
vert = append(vert, 'v')
}
}
if curr.X == 0 && dest.Y == 1 {
seq = append(seq, horiz...)
seq = append(seq, vert...)
} else if curr.Y == 1 && dest.X == 0 {
seq = append(seq, vert...)
seq = append(seq, horiz...)
} else if dx < 0 {
seq = append(seq, horiz...)
seq = append(seq, vert...)
} else {
seq = append(seq, vert...)
seq = append(seq, horiz...)
}
curr = dest
seq = append(seq, 'A')
}
return string(seq)
}
func getNumeric(input string, start rune) string {
curr := numeric.GetButtonPosition(start)
seq := []rune{}
for _, char := range input {
dest := numeric.GetButtonPosition(char)
dx, dy := dest.X-curr.X, dest.Y-curr.Y
horiz, vert := []rune{}, []rune{}
for i := 0; i < int(math.Abs(float64(dx))); i++ {
if dx >= 0 {
horiz = append(horiz, '>')
} else {
horiz = append(horiz, '<')
}
}
for i := 0; i < int(math.Abs(float64(dy))); i++ {
if dy >= 0 {
vert = append(vert, '^')
} else {
vert = append(vert, 'v')
}
}
if curr.Y == 0 && dest.X == 0 {
seq = append(seq, vert...)
seq = append(seq, horiz...)
} else if curr.X == 0 && dest.Y == 0 {
seq = append(seq, horiz...)
seq = append(seq, vert...)
} else if dx < 0 {
seq = append(seq, horiz...)
seq = append(seq, vert...)
} else {
seq = append(seq, vert...)
seq = append(seq, horiz...)
}
curr = dest
seq = append(seq, 'A')
}
return string(seq)
}
func split(input string) []string {
var result []string
var current string
for _, char := range input {
current += string(char)
if char == 'A' {
result = append(result, current)
current = ""
}
}
return result
}
func count(input string, maxRobots, robot int) int {
if val, ok := cache[input]; ok && robot <= len(val) && val[robot-1] != 0 {
return val[robot-1]
}
seq := getDirectional(input, 'A')
if robot == maxRobots {
return len(seq)
}
if _, ok := cache[input]; !ok {
cache[input] = make([]int, maxRobots)
}
steps := split(seq)
total := 0
for _, step := range steps {
c := count(step, maxRobots, robot+1)
total += c
}
cache[input][robot-1] = total
return total
}
var cache map[string][]int
func Part1(sequences []string) int {
cache = make(map[string][]int)
re := regexp.MustCompile(`\d+`)
complexity := 0
for _, sequence := range sequences {
numericCode := re.FindString(sequence)
value, err := strconv.Atoi(numericCode)
if err != nil {
panic(err)
}
moves := getNumeric(sequence, 'A')
length := count(moves, 2, 1)
complexity += length * value
}
return complexity
}
func Part2(sequences []string) int {
cache = make(map[string][]int)
re := regexp.MustCompile(`\d+`)
complexity := 0
for _, sequence := range sequences {
numericCode := re.FindString(sequence)
value, err := strconv.Atoi(numericCode)
if err != nil {
panic(err)
}
moves := getNumeric(sequence, 'A')
length := count(moves, 25, 1)
complexity += length * value
}
return complexity
}
func Run(filename string) (interface{}, interface{}) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
var sequences []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if scanner.Text() == "" {
break
}
sequences = append(sequences, scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
part1 := Part1(sequences)
part2 := Part2(sequences)
return part1, part2
}

34
2024/day21/day21_test.go Executable file
View file

@ -0,0 +1,34 @@
package day21
import (
"os"
"testing"
)
type TestCase struct {
FileName string
Part1 interface{}
Part2 interface{}
}
func TestDay21(t *testing.T) {
tests := []TestCase{
{"test.txt", 126384, 154115708116294},
{"input.txt", 176452, 218309335714068},
}
for _, test := range tests {
if _, err := os.Stat(test.FileName); os.IsNotExist(err) {
t.Fatalf("test file does not exist: %v", test.FileName)
}
part1, part2 := Run(test.FileName)
if test.Part1 != nil && part1 != test.Part1 {
t.Fatalf("%s: unexpected part1:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part1, part1)
}
if test.Part2 != nil && part2 != test.Part2 {
t.Fatalf("%s: unexpected part2:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part2, part2)
}
}
}

5
2024/day21/input.txt Executable file
View file

@ -0,0 +1,5 @@
480A
682A
140A
246A
938A

5
2024/day21/test.txt Executable file
View file

@ -0,0 +1,5 @@
029A
980A
179A
456A
379A