From e0fa0f507c827fda095bb44e129afb1b2f5d4914 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Sun, 22 Sep 2024 10:09:46 -0700 Subject: [PATCH] Move Go solutions and add a new Go framework --- go/2017.17.go | 58 +++++++++++++++++++++++++ go/2017.22.go | 83 +++++++++++++++++++++++++++++++++++ go/2020.01.go | 63 +++++++++++++++++++++++++++ go/aoc.go | 20 +++++++++ go/framework.go | 69 +++++++++++++++++++++++++++++ go/go.mod | 3 ++ go/helpers.go | 65 ++++++++++++++++++++++++++++ other/go/2017.17.go | 49 --------------------- other/go/2017.22.go | 103 -------------------------------------------- other/go/2020.01.go | 55 ----------------------- 10 files changed, 361 insertions(+), 207 deletions(-) create mode 100644 go/2017.17.go create mode 100644 go/2017.22.go create mode 100644 go/2020.01.go create mode 100644 go/aoc.go create mode 100644 go/framework.go create mode 100644 go/go.mod create mode 100644 go/helpers.go delete mode 100644 other/go/2017.17.go delete mode 100644 other/go/2017.22.go delete mode 100644 other/go/2020.01.go diff --git a/go/2017.17.go b/go/2017.17.go new file mode 100644 index 0000000..f8720ca --- /dev/null +++ b/go/2017.17.go @@ -0,0 +1,58 @@ +package main + +// P201717 solves 2017/17. +type P201717 struct { + steps int +} + +type node struct { + val int + next *node +} + +func partOne(step int) int { + list := &node{0, nil} + list.next = list + + for size := 1; size <= 2017; size++ { + stepsNeeded := step % size + for i := 0; i < stepsNeeded; i++ { + list = list.next + } + newNode := &node{size, list.next} + list.next = newNode + list = list.next + } + + return list.next.val +} + +func partTwo(step int) int { + lastVal := 0 + pos := 0 + + for size := 1; size <= 50000000; size++ { + pos = ((pos + step) % size) + 1 + if pos == 1 { + lastVal = size + } + } + + return lastVal +} + +// New201717 returns a new solver for 2017/17. +func New201717() *P201717 { + return &P201717{} +} + +// SetInput handles input for this solver. +func (p *P201717) SetInput(data string) { + p.steps = Atoi(data) +} + +// Solve returns the solution for one part. +func (p *P201717) Solve(part int) string { + m := []func(int) int{partOne, partTwo}[part] + return Itoa(m(p.steps)) +} diff --git a/go/2017.22.go b/go/2017.22.go new file mode 100644 index 0000000..b3d73c1 --- /dev/null +++ b/go/2017.22.go @@ -0,0 +1,83 @@ +package main + +import ( + "maps" + "slices" + "strings" +) + +type virusState int + +// P201722 solves 2017/22. +type P201722 struct { + nodes map[Location]virusState + center int +} + +const ( + clean virusState = iota + weakened virusState = iota + infected virusState = iota + flagged virusState = iota +) + +var ( + steps = []int{10000, 10000000} + nextState = []map[virusState]virusState{ + {clean: infected, infected: clean}, + {clean: weakened, weakened: infected, infected: flagged, flagged: clean}, + } + rotations = map[virusState]Rotation{clean: RotateLeft, weakened: RotateStraight, infected: RotateRight, flagged: RotateReverse} +) + +type simulation struct { + *Robot + nodes map[Location]virusState + infected int +} + +func (s *simulation) run(steps int, states map[virusState]virusState) { + for range steps { + state, ok := s.nodes[s.Location] + if !ok { + state = clean + } + s.Direction.Rotate(rotations[state]) + s.nodes[s.Location] = states[state] + if s.nodes[s.Location] == infected { + s.infected++ + } + s.Robot.Advance() + } +} + +// New201722 returns a new solver for 2017/22. +func New201722() *P201722 { + return &P201722{} +} + +// SetInput handles input for this solver. +func (p *P201722) SetInput(data string) { + lines := strings.Split(data, "\n") + slices.Reverse(lines) + p.nodes = make(map[Location]virusState) + for y, line := range lines { + for x, char := range line { + if char == '#' { + p.nodes[Location{x, y}] = infected + } + } + } + p.center = (len(lines) - 1) / 2 +} + +// Solve returns the solution for one part. +func (p *P201722) Solve(part int) string { + s := simulation{ + Robot: &Robot{Location{p.center, p.center}, Direction{0, 1}}, + nodes: maps.Clone(p.nodes), + infected: 0, + } + s.run(steps[part], nextState[part]) + return Itoa(s.infected) +} diff --git a/go/2020.01.go b/go/2020.01.go new file mode 100644 index 0000000..535f342 --- /dev/null +++ b/go/2020.01.go @@ -0,0 +1,63 @@ +package main + +import ( + "strings" +) + +const ( + target = 2020 +) + +func check(err error) { + if err != nil { + panic(err) + } +} + +// P202001 solves 2020/01. +type P202001 struct { + nums map[int]bool +} + +// New202001 returns a new solver for 2020/01. +func New202001() *P202001 { + return &P202001{} +} + +// SetInput handles input for this solver. +func (p *P202001) SetInput(data string) { + p.nums = make(map[int]bool) + for _, s := range strings.Split(string(data), "\n") { + if s == "" { + continue + } + n := Atoi(s) + p.nums[n] = true + } +} + +func (p *P202001) partOne() int { + for n := range p.nums { + if _, has := p.nums[target-n]; has != false { + return n * (target - n) + } + } + panic("no solution found") +} +func (p *P202001) partTwo() int { + for n := range p.nums { + for o := range p.nums { + t := target - n - o + if _, has := p.nums[t]; has != false { + return n * o * t + } + } + } + panic("no solution found") +} + +// Solve returns the solution for one part. +func (p *P202001) Solve(part int) string { + m := []func() int{p.partOne, p.partTwo}[part] + return Itoa(m()) +} diff --git a/go/aoc.go b/go/aoc.go new file mode 100644 index 0000000..bbc1107 --- /dev/null +++ b/go/aoc.go @@ -0,0 +1,20 @@ +package main + +import "os" + +var puzzles = map[Puzzle]Solver{ + Puzzle{2017, 17}: New201717(), + Puzzle{2017, 22}: New201722(), + Puzzle{2020, 1}: New202001(), +} + +func main() { + if len(os.Args) == 3 { + p := Puzzle{Atoi(os.Args[1]), Atoi(os.Args[2])} + p.Check(puzzles[p]) + } else { + for puzzle, solver := range puzzles { + puzzle.Check(solver) + } + } +} diff --git a/go/framework.go b/go/framework.go new file mode 100644 index 0000000..f744e3e --- /dev/null +++ b/go/framework.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "io/ioutil" + "strings" + "time" +) + +// Solver is able to solve the puzzle for a day. +type Solver interface { + Solve(int) string + SetInput(string) +} + +// Puzzle is the challenge for a day. +type Puzzle struct { + year, day int +} + +// ReadData returns the puzzle input data. +func (p Puzzle) ReadData() string { + filename := fmt.Sprintf("../inputs/%d.%02d.txt", p.year, p.day) + data, err := ioutil.ReadFile(filename) + if err != nil { + panic("Failed to read file") + } + return strings.TrimRight(string(data), "\n") +} + +// Check checks if a Solver can solve a puzzle. +func (p Puzzle) Check(solver Solver) { + solutions, err := p.Solutions() + if err != nil { + fmt.Println("failed to load solutions: %v", err) + } + solver.SetInput(p.ReadData()) + for i := 0; i < 2; i++ { + start := time.Now() + got := solver.Solve(i) + elapsed := time.Now().Sub(start) + if got == solutions[i] { + fmt.Printf("%d/%02d.%d PASSED! %12s\n", p.year, p.day, i+1, elapsed) + } else { + fmt.Printf("%d/%02d.%d FAILED!\n", p.year, p.day, i+1) + fmt.Printf("want %s but got %s\n", solutions[i], got) + } + } +} + +// Solutions returns the solutions from the solution file. +func (p Puzzle) Solutions() ([]string, error) { + filename := fmt.Sprintf("../solutions/%d.txt", p.year) + day := fmt.Sprintf("%d", p.day) + data, err := ioutil.ReadFile(filename) + if err != nil { + panic("Failed to read file") + } + for _, line := range strings.Split(strings.TrimRight(string(data), "\n"), "\n") { + if line == "" { + continue + } + words := strings.Split(line, " ") + if words[0] == day { + return words[1:], nil + } + } + return nil, fmt.Errorf("Failed to load solutions") +} diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..7a1573a --- /dev/null +++ b/go/go.mod @@ -0,0 +1,3 @@ +module aoc + +go 1.22 diff --git a/go/helpers.go b/go/helpers.go new file mode 100644 index 0000000..0a43b6b --- /dev/null +++ b/go/helpers.go @@ -0,0 +1,65 @@ +package main + +import ( + "strconv" +) + +// Rotation encodes a rotation of n * 90 degrees. +type Rotation int + +// Rotations in four directions. +const ( + RotateRight Rotation = iota + RotateLeft Rotation = iota + RotateReverse Rotation = iota + RotateStraight Rotation = iota +) + +// Direction tracks a 2D vector. +type Direction struct { + dx, dy int +} + +// Rotate the direction by n * 90 degrees. +func (d *Direction) Rotate(rotation Rotation) { + switch rotation { + case RotateRight: + d.dx, d.dy = +1*d.dy, -1*d.dx + case RotateLeft: + d.dx, d.dy = -1*d.dy, +1*d.dx + case RotateReverse: + d.dx, d.dy = -1*d.dx, -1*d.dy + case RotateStraight: + } +} + +// Location tracks a 2D Cartesian coordinate. +type Location struct { + x, y int +} + +// Robot is an object with a Cartesian location and direction. It can advance and rotate. +type Robot struct { + Location + Direction +} + +// Advance the robot by the Direction. +func (r *Robot) Advance() { + r.x += r.dx + r.y += r.dy +} + +// Atoi is a convenience wrapper around strconv.Atoi +func Atoi(a string) int{ + i, err := strconv.Atoi(a) + if err != nil { + panic("strconv.Atoi failed") + } + return i +} + +// Itoa is a convenience wrapper around strconv.Itoa +func Itoa(i int) string{ + return strconv.Itoa(i) +} diff --git a/other/go/2017.17.go b/other/go/2017.17.go deleted file mode 100644 index c9d53bd..0000000 --- a/other/go/2017.17.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "fmt" -) - -type Node struct { - val int - next *Node -} - -func partOne(step int) int { - list := &Node{0, nil} - list.next = list - - for size := 1; size <= 2017; size++ { - stepsNeeded := step % size - for i := 0; i < stepsNeeded; i++ { - list = list.next - } - newNode := &Node{size, list.next} - list.next = newNode - list = list.next - } - - return list.next.val -} - -func partTwo(step int) int { - lastVal := 0 - pos := 0 - - for size := 1; size <= 50000000; size++ { - pos = ((pos + step) % size) + 1 - if pos == 1 { - lastVal = size - } - } - - return lastVal -} - -func main() { - testStep := 3 - realStep := 348 - fmt.Printf("partOne(%d) = %d\n", testStep, partOne(testStep)) - fmt.Printf("partOne(%d) = %d\n", realStep, partOne(realStep)) - fmt.Printf("partTwo(%d) = %d\n", realStep, partTwo(realStep)) -} diff --git a/other/go/2017.22.go b/other/go/2017.22.go deleted file mode 100644 index ed02fb8..0000000 --- a/other/go/2017.22.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "maps" - "slices" - "strings" -) - -type Rotation int -type virusState int - -const ( - rotateRight Rotation = iota - rotateLeft Rotation = iota - rotateReverse Rotation = iota - rotateStraight Rotation = iota - clean virusState = iota - weakened virusState = iota - infected virusState = iota - flagged virusState = iota - steps1 = 10000 - steps2 = 10000000 -) - -var ( - nextState1 = map[virusState]virusState{clean: infected, infected: clean} - nextState2 = map[virusState]virusState{clean: weakened, weakened: infected, infected: flagged, flagged: clean} - rotations = map[virusState]Rotation{clean: rotateLeft, weakened: rotateStraight, infected: rotateRight, flagged: rotateReverse} -) - -type Location struct { - x, y int -} - -func (l *Location) Advance(d *Direction) { - l.x += d.dx - l.y += d.dy -} - -type Direction struct { - dx, dy int -} - -func (d *Direction) Rotate(rotation Rotation) { - switch rotation { - case rotateRight: - d.dx, d.dy = +1*d.dy, -1*d.dx - case rotateLeft: - d.dx, d.dy = -1*d.dy, +1*d.dx - case rotateReverse: - d.dx, d.dy = -1*d.dx, -1*d.dy - case rotateStraight: - } -} - -type Simulation struct { - *Location - *Direction - nodes map[Location]virusState - infected int -} - -func (s *Simulation) Run(steps int, states map[virusState]virusState) { - for range steps { - state, ok := s.nodes[*s.Location] - if !ok { - state = clean - } - s.Direction.Rotate(rotations[state]) - s.nodes[*s.Location] = states[state] - if s.nodes[*s.Location] == infected { - s.infected++ - } - s.Advance(s.Direction) - } -} - -func main() { - data, err := ioutil.ReadFile("2017/22.txt") - if err != nil { - panic("Failed to read file") - } - lines := strings.Split(strings.TrimRight(string(data), "\n"), "\n") - slices.Reverse(lines) - nodes := make(map[Location]virusState) - for y, line := range lines { - for x, char := range line { - if char == '#' { - nodes[Location{x, y}] = infected - } - } - } - center := (len(lines) - 1) / 2 - nodes2 := maps.Clone(nodes) - s := &Simulation{&Location{center, center}, &Direction{0, 1}, nodes, 0} - s.Run(steps1, nextState1) - fmt.Printf("Part 1: Robot infected %d locations. Correct: %v\n", s.infected, s.infected == 5261) - s = &Simulation{&Location{center, center}, &Direction{0, 1}, nodes2, 0} - s.Run(steps2, nextState2) - fmt.Printf("Part 2: Robot infected %d locations. Correct: %v\n", s.infected, s.infected == 2511927) -} diff --git a/other/go/2020.01.go b/other/go/2020.01.go deleted file mode 100644 index 3a584ee..0000000 --- a/other/go/2020.01.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" -) - -func check(err error) { - if err != nil { - panic(err) - } -} - -func main() { - nums := make(map[int]bool) - target := 2020 - - data, err := ioutil.ReadFile(os.Args[1]) - check(err) - - for _, s := range strings.Split(string(data), "\n") { - if s == "" { - continue - } - n, err := strconv.Atoi(s) - check(err) - - nums[n] = true - } - - for n, _ := range nums { - if _, has := nums[target-n]; has != false { - fmt.Printf("Part one: %d\n", n*(target-n)) - break - } - } - - found := false - for n, _ := range nums { - for o, _ := range nums { - p := target - n - o - if _, has := nums[p]; has != false { - fmt.Printf("Part two: %d\n", n*o*p) - found = true - break - } - } - if found { - break - } - } -}