From 567ca724027770c8c29fc895d7e22573c252705b Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Fri, 6 Dec 2024 20:01:17 +0000 Subject: [PATCH] add day-06 + benchmarks --- day-06/benchmark | 1 + day-06/cmd/cli/main.go | 41 +++++++++++++ day-06/go.mod | 7 +++ day-06/go.sum | 15 +++++ day-06/internal/one/benchmark | 6 ++ day-06/internal/one/direction.go | 8 +++ day-06/internal/one/graph.go | 30 ++++++++++ day-06/internal/one/point.go | 51 ++++++++++++++++ day-06/internal/one/solve.go | 51 ++++++++++++++++ day-06/internal/one/solve_internal_test.go | 15 +++++ day-06/internal/one/util.go | 29 ++++++++++ day-06/internal/two/benchmark | 6 ++ day-06/internal/two/direction.go | 9 +++ day-06/internal/two/graph.go | 35 +++++++++++ day-06/internal/two/point.go | 47 +++++++++++++++ day-06/internal/two/solve.go | 67 ++++++++++++++++++++++ day-06/internal/two/solve_internal_test.go | 15 +++++ day-06/internal/two/util.go | 42 ++++++++++++++ day-06/internal/util/util.go | 7 +++ day-06/makefile | 30 ++++++++++ day-06/solve.go | 20 +++++++ 21 files changed, 532 insertions(+) create mode 100644 day-06/benchmark create mode 100644 day-06/cmd/cli/main.go create mode 100644 day-06/go.mod create mode 100644 day-06/go.sum create mode 100644 day-06/internal/one/benchmark create mode 100644 day-06/internal/one/direction.go create mode 100644 day-06/internal/one/graph.go create mode 100644 day-06/internal/one/point.go create mode 100644 day-06/internal/one/solve.go create mode 100644 day-06/internal/one/solve_internal_test.go create mode 100644 day-06/internal/one/util.go create mode 100644 day-06/internal/two/benchmark create mode 100644 day-06/internal/two/direction.go create mode 100644 day-06/internal/two/graph.go create mode 100644 day-06/internal/two/point.go create mode 100644 day-06/internal/two/solve.go create mode 100644 day-06/internal/two/solve_internal_test.go create mode 100644 day-06/internal/two/util.go create mode 100644 day-06/internal/util/util.go create mode 100644 day-06/makefile create mode 100644 day-06/solve.go diff --git a/day-06/benchmark b/day-06/benchmark new file mode 100644 index 0000000..1c69ef6 --- /dev/null +++ b/day-06/benchmark @@ -0,0 +1 @@ +? github.com/onyx-and-iris/aoc2024/day-06 [no test files] diff --git a/day-06/cmd/cli/main.go b/day-06/cmd/cli/main.go new file mode 100644 index 0000000..1eadc96 --- /dev/null +++ b/day-06/cmd/cli/main.go @@ -0,0 +1,41 @@ +/******************************************************************************** + Advent of Code 2024 - day-06 +********************************************************************************/ + +package main + +import ( + "embed" + "flag" + "fmt" + "slices" + + log "github.com/sirupsen/logrus" + + problems "github.com/onyx-and-iris/aoc2024/day-06" +) + +//go:embed testdata +var files embed.FS + +func main() { + filename := flag.String("f", "input.txt", "input file") + loglevel := flag.Int("l", int(log.InfoLevel), "log level") + flag.Parse() + + if slices.Contains(log.AllLevels, log.Level(*loglevel)) { + log.SetLevel(log.Level(*loglevel)) + } + + data, err := files.ReadFile(fmt.Sprintf("testdata/%s", *filename)) + if err != nil { + log.Fatal(err) + } + + one, two, err := problems.Solve(data) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("solution one: %d\nsolution two: %d\n", one, two) +} diff --git a/day-06/go.mod b/day-06/go.mod new file mode 100644 index 0000000..1c23d8f --- /dev/null +++ b/day-06/go.mod @@ -0,0 +1,7 @@ +module github.com/onyx-and-iris/aoc2024/day-06 + +go 1.23.3 + +require github.com/sirupsen/logrus v1.9.3 + +require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/day-06/go.sum b/day-06/go.sum new file mode 100644 index 0000000..21f9bfb --- /dev/null +++ b/day-06/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/day-06/internal/one/benchmark b/day-06/internal/one/benchmark new file mode 100644 index 0000000..aa188ac --- /dev/null +++ b/day-06/internal/one/benchmark @@ -0,0 +1,6 @@ +goos: linux +goarch: amd64 +pkg: github.com/onyx-and-iris/aoc2024/day-06/internal/one +cpu: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz +BenchmarkSolve-12 1000000000 0.007368 ns/op +ok github.com/onyx-and-iris/aoc2024/day-06/internal/one 0.052s diff --git a/day-06/internal/one/direction.go b/day-06/internal/one/direction.go new file mode 100644 index 0000000..bbb6473 --- /dev/null +++ b/day-06/internal/one/direction.go @@ -0,0 +1,8 @@ +package one + +const ( + N = iota + E + S + W +) diff --git a/day-06/internal/one/graph.go b/day-06/internal/one/graph.go new file mode 100644 index 0000000..9a9c9d3 --- /dev/null +++ b/day-06/internal/one/graph.go @@ -0,0 +1,30 @@ +package one + +import ( + "strings" + + "github.com/onyx-and-iris/aoc2024/day-06/internal/util" +) + +type graph struct { + data []string + startPoint point +} + +func newGraph() *graph { + return &graph{} +} + +func (g *graph) String() string { + return strings.Join(g.data, "\n") +} + +func (g *graph) debug(visited map[coords]struct{}) string { + for loc := range visited { + if !(rune(g.data[loc.Y][loc.X]) == 'O') { + g.data[loc.Y] = util.ReplaceAtIndex(g.data[loc.Y], '+', loc.X) + } + } + + return g.String() +} diff --git a/day-06/internal/one/point.go b/day-06/internal/one/point.go new file mode 100644 index 0000000..15f0637 --- /dev/null +++ b/day-06/internal/one/point.go @@ -0,0 +1,51 @@ +package one + +import "fmt" + +type coords struct { + X int + Y int +} + +type point struct { + coords + direction int +} + +func (p *point) String() string { + return fmt.Sprintf("X: %d Y:%d Direction: %s", p.X, p.Y, []string{"N", "E", "S", "W"}[p.direction]) +} + +func (p *point) recalibrate() { + switch p.direction { + case N: + p.Y++ + case E: + p.X-- + case S: + p.Y-- + case W: + p.X++ + } + + if p.direction == W { + p.direction = 0 + } else { + p.direction++ + } +} + +func nextPoint(current point) point { + switch current.direction { + case N: + return point{coords{current.X, current.Y - 1}, current.direction} + case E: + return point{coords{current.X + 1, current.Y}, current.direction} + case S: + return point{coords{current.X, current.Y + 1}, current.direction} + case W: + return point{coords{current.X - 1, current.Y}, current.direction} + default: + return point{} + } +} diff --git a/day-06/internal/one/solve.go b/day-06/internal/one/solve.go new file mode 100644 index 0000000..0c7c9c4 --- /dev/null +++ b/day-06/internal/one/solve.go @@ -0,0 +1,51 @@ +package one + +import ( + "bytes" + + log "github.com/sirupsen/logrus" +) + +var Visited = make(map[coords]struct{}) + +func Solve(buf []byte) (int, error) { + r := bytes.NewReader(buf) + graph, err := parseLines(r) + if err != nil { + return 0, err + } + log.Debug(graph.startPoint.String()) + + var count int + count = nextStep(graph.startPoint, graph, Visited, count) + log.Debugf("path walked: \n%s\n", graph.debug(Visited)) + + return count, nil +} + +func nextStep(point point, graph *graph, visited map[coords]struct{}, count int) int { + _, ok := visited[point.coords] + if !ok { + visited[point.coords] = struct{}{} + count++ + } + + log.Debugf("count: %d\n", count) + + if point.X == 0 && point.direction == W || + point.Y == 0 && point.direction == N || + point.Y == len(graph.data)-1 && point.direction == S || + point.X == len(graph.data[point.Y])-1 && point.direction == E { + return count + } + + next := nextPoint(point) + log.Debug(next.String()) + log.Debug(string(graph.data[next.Y][next.X])) + if graph.data[next.Y][next.X] == '#' { + next.recalibrate() + log.Debugf("switched direction to %d", next.direction) + } + + return nextStep(next, graph, visited, count) +} diff --git a/day-06/internal/one/solve_internal_test.go b/day-06/internal/one/solve_internal_test.go new file mode 100644 index 0000000..e5fd7ea --- /dev/null +++ b/day-06/internal/one/solve_internal_test.go @@ -0,0 +1,15 @@ +package one + +import ( + _ "embed" + "os" + "testing" +) + +//go:embed testdata/input.txt +var data []byte + +func BenchmarkSolve(b *testing.B) { + os.Stdout, _ = os.Open(os.DevNull) + Solve(data) +} diff --git a/day-06/internal/one/util.go b/day-06/internal/one/util.go new file mode 100644 index 0000000..5ad2b68 --- /dev/null +++ b/day-06/internal/one/util.go @@ -0,0 +1,29 @@ +package one + +import ( + "bufio" + "io" + "strings" +) + +func parseLines(r io.Reader) (*graph, error) { + graph := newGraph() + + count := 0 + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + indx := strings.Index(line, "^") + if indx != -1 { + graph.startPoint = point{coords{indx, count}, N} + } + graph.data = append(graph.data, scanner.Text()) + count++ + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return graph, nil +} diff --git a/day-06/internal/two/benchmark b/day-06/internal/two/benchmark new file mode 100644 index 0000000..3c8ddec --- /dev/null +++ b/day-06/internal/two/benchmark @@ -0,0 +1,6 @@ +goos: linux +goarch: amd64 +pkg: github.com/onyx-and-iris/aoc2024/day-06/internal/two +cpu: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz +BenchmarkSolve-12 1000000000 0.0000547 ns/op +ok github.com/onyx-and-iris/aoc2024/day-06/internal/two 0.006s diff --git a/day-06/internal/two/direction.go b/day-06/internal/two/direction.go new file mode 100644 index 0000000..cc2ba02 --- /dev/null +++ b/day-06/internal/two/direction.go @@ -0,0 +1,9 @@ +package two + +const ( + N = iota + E + S + W + obstacle +) diff --git a/day-06/internal/two/graph.go b/day-06/internal/two/graph.go new file mode 100644 index 0000000..727afb3 --- /dev/null +++ b/day-06/internal/two/graph.go @@ -0,0 +1,35 @@ +package two + +import ( + "strings" + + "github.com/onyx-and-iris/aoc2024/day-06/internal/util" +) + +type graph struct { + data []string + startPoint point + obstacles []point +} + +func newGraph() *graph { + return &graph{} +} + +func (g *graph) String() string { + return strings.Join(g.data, "\n") +} + +func (g *graph) valueAt(x, y int) rune { + return rune(g.data[y][x]) +} + +func (g *graph) trace(visited map[point]struct{}) string { + for loc := range visited { + if !(rune(g.data[loc.y][loc.x]) == 'O') { + g.data[loc.y] = util.ReplaceAtIndex(g.data[loc.y], '+', loc.x) + } + } + + return g.String() +} diff --git a/day-06/internal/two/point.go b/day-06/internal/two/point.go new file mode 100644 index 0000000..4c23e28 --- /dev/null +++ b/day-06/internal/two/point.go @@ -0,0 +1,47 @@ +package two + +import "fmt" + +type point struct { + x int + y int + direction int +} + +func (p *point) String() string { + return fmt.Sprintf("X: %d Y:%d Direction: %s", p.x, p.y, []string{"N", "E", "S", "W", "Obs"}[p.direction]) +} + +func (p *point) recalibrate() { + switch p.direction { + case N: + p.y++ + case E: + p.x-- + case S: + p.y-- + case W: + p.x++ + } + + if p.direction == W { + p.direction = 0 + } else { + p.direction++ + } +} + +func nextPoint(current point) point { + switch current.direction { + case N: + return point{current.x, current.y - 1, current.direction} + case E: + return point{current.x + 1, current.y, current.direction} + case S: + return point{current.x, current.y + 1, current.direction} + case W: + return point{current.x - 1, current.y, current.direction} + default: + return point{} + } +} diff --git a/day-06/internal/two/solve.go b/day-06/internal/two/solve.go new file mode 100644 index 0000000..caefdfc --- /dev/null +++ b/day-06/internal/two/solve.go @@ -0,0 +1,67 @@ +package two + +import ( + "bytes" + "slices" + + "github.com/onyx-and-iris/aoc2024/day-06/internal/one" + + log "github.com/sirupsen/logrus" +) + +func Solve(buf []byte) (int, error) { + r := bytes.NewReader(buf) + graph, err := parseLines(r) + if err != nil { + return 0, err + } + + conc := len(one.Visited) - 1 + isLoop := make(chan bool) + + for loc := range one.Visited { + if loc.X == graph.startPoint.x && loc.Y == graph.startPoint.y { + continue + } + + g := newGraph() + g.startPoint = graph.startPoint + g.data = slices.Clone(graph.data) + g.data[loc.Y] = replaceAtIndex(g.data[loc.Y], 'O', loc.X) + + p := g.startPoint + go func() { + visited := make(map[point]struct{}) + + for !(p.x == 0 && p.direction == W || + p.y == 0 && p.direction == N || + p.y == len(g.data)-1 && p.direction == S || + p.x == len(g.data[p.y])-1 && p.direction == E) { + p = nextPoint(p) + if g.valueAt(p.x, p.y) == '#' || g.valueAt(p.x, p.y) == 'O' { + p.recalibrate() + } + + _, ok := visited[p] + if !ok { + visited[p] = struct{}{} + } else { + log.Tracef("loop: \n%s\n", g.trace(visited)) + isLoop <- true + return + } + } + isLoop <- false + }() + } + + var numLoops int + for range conc { + res := <-isLoop + if res { + numLoops++ + } + } + + return numLoops, nil +} diff --git a/day-06/internal/two/solve_internal_test.go b/day-06/internal/two/solve_internal_test.go new file mode 100644 index 0000000..ecb05d5 --- /dev/null +++ b/day-06/internal/two/solve_internal_test.go @@ -0,0 +1,15 @@ +package two + +import ( + _ "embed" + "os" + "testing" +) + +//go:embed testdata/input.txt +var data []byte + +func BenchmarkSolve(b *testing.B) { + os.Stdout, _ = os.Open(os.DevNull) + Solve(data) +} diff --git a/day-06/internal/two/util.go b/day-06/internal/two/util.go new file mode 100644 index 0000000..3d9e240 --- /dev/null +++ b/day-06/internal/two/util.go @@ -0,0 +1,42 @@ +package two + +import ( + "bufio" + "io" + "strings" +) + +func parseLines(r io.Reader) (*graph, error) { + graph := newGraph() + + count := 0 + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + indx := strings.Index(line, "^") + if indx != -1 { + graph.startPoint = point{indx, count, N} + } + graph.data = append(graph.data, scanner.Text()) + + for i, r := range line { + if r == '#' { + graph.obstacles = append(graph.obstacles, point{i, count, obstacle}) + } + } + + count++ + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return graph, nil +} + +func replaceAtIndex(s string, r rune, i int) string { + out := []rune(s) + out[i] = r + return string(out) +} diff --git a/day-06/internal/util/util.go b/day-06/internal/util/util.go new file mode 100644 index 0000000..dd8e76a --- /dev/null +++ b/day-06/internal/util/util.go @@ -0,0 +1,7 @@ +package util + +func ReplaceAtIndex(s string, r rune, i int) string { + out := []rune(s) + out[i] = r + return string(out) +} diff --git a/day-06/makefile b/day-06/makefile new file mode 100644 index 0000000..6774aaa --- /dev/null +++ b/day-06/makefile @@ -0,0 +1,30 @@ +program = day-06 + +GO = go +SRC_DIR := src +BIN_DIR := bin + +EXE := $(BIN_DIR)/$(program) + +.DEFAULT_GOAL := build + +.PHONY: fmt vet build bench clean +fmt: + $(GO) fmt ./... + +vet: fmt + $(GO) vet ./... + +build: vet | $(BIN_DIR) + $(GO) build -o $(EXE) ./$(SRC_DIR) + +bench: + $(GO) test ./internal/one/ -bench=. > internal/one/benchmark + $(GO) test ./internal/two/ -bench=. > internal/two/benchmark + $(GO) test . -count=10 -bench=. > benchmark + +$(BIN_DIR): + @mkdir -p $@ + +clean: + @rm -rv $(BIN_DIR) diff --git a/day-06/solve.go b/day-06/solve.go new file mode 100644 index 0000000..c570c79 --- /dev/null +++ b/day-06/solve.go @@ -0,0 +1,20 @@ +package name + +import ( + "github.com/onyx-and-iris/aoc2024/day-06/internal/one" + "github.com/onyx-and-iris/aoc2024/day-06/internal/two" +) + +func Solve(buf []byte) (int, int, error) { + answerOne, err := one.Solve(buf) + if err != nil { + return 0, 0, err + } + + answerTwo, err := two.Solve(buf) + if err != nil { + return 0, 0, err + } + + return answerOne, answerTwo, nil +}