Skip to content

Commit

Permalink
Move Go solutions and add a new Go framework
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaacG committed Sep 22, 2024
1 parent abaf413 commit e0fa0f5
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 207 deletions.
58 changes: 58 additions & 0 deletions go/2017.17.go
Original file line number Diff line number Diff line change
@@ -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))
}
83 changes: 83 additions & 0 deletions go/2017.22.go
Original file line number Diff line number Diff line change
@@ -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)
}
63 changes: 63 additions & 0 deletions go/2020.01.go
Original file line number Diff line number Diff line change
@@ -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())
}
20 changes: 20 additions & 0 deletions go/aoc.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
69 changes: 69 additions & 0 deletions go/framework.go
Original file line number Diff line number Diff line change
@@ -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")
}
3 changes: 3 additions & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module aoc

go 1.22
65 changes: 65 additions & 0 deletions go/helpers.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit e0fa0f5

Please sign in to comment.