From adc38bce20392b559804f64ee5ba55cf03c61484 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 26 May 2021 01:27:51 +0300 Subject: [PATCH 1/7] 2016/day01: initial commit --- internal/input/data/2016/day01.txt | 1 + .../puzzles/solutions/2016/day01/solution.go | 40 ++++++ .../solutions/2016/day01/solution_test.go | 123 ++++++++++++++++++ internal/puzzles/solutions/2016/day01/spec.md | 28 ++++ 4 files changed, 192 insertions(+) create mode 100644 internal/input/data/2016/day01.txt create mode 100644 internal/puzzles/solutions/2016/day01/solution.go create mode 100644 internal/puzzles/solutions/2016/day01/solution_test.go create mode 100644 internal/puzzles/solutions/2016/day01/spec.md diff --git a/internal/input/data/2016/day01.txt b/internal/input/data/2016/day01.txt new file mode 100644 index 00000000..0c1b018b --- /dev/null +++ b/internal/input/data/2016/day01.txt @@ -0,0 +1 @@ +R1, R3, L2, L5, L2, L1, R3, L4, R2, L2, L4, R2, L1, R1, L2, R3, L1, L4, R2, L5, R3, R4, L1, R2, L1, R3, L4, R5, L4, L5, R5, L3, R2, L3, L3, R1, R3, L4, R2, R5, L4, R1, L1, L1, R5, L2, R1, L2, R188, L5, L3, R5, R1, L2, L4, R3, R5, L3, R3, R45, L4, R4, R72, R2, R3, L1, R1, L1, L1, R192, L1, L1, L1, L4, R1, L2, L5, L3, R5, L3, R3, L4, L3, R1, R4, L2, R2, R3, L5, R3, L1, R1, R4, L2, L3, R1, R3, L4, L3, L4, L2, L2, R1, R3, L5, L1, R4, R2, L4, L1, R3, R3, R1, L5, L2, R4, R4, R2, R1, R5, R5, L4, L1, R5, R3, R4, R5, R3, L1, L2, L4, R1, R4, R5, L2, L3, R4, L4, R2, L2, L4, L2, R5, R1, R4, R3, R5, L4, L4, L5, L5, R3, R4, L1, L3, R2, L2, R1, L3, L5, R5, R5, R3, L4, L2, R4, R5, R1, R4, L3 \ No newline at end of file diff --git a/internal/puzzles/solutions/2016/day01/solution.go b/internal/puzzles/solutions/2016/day01/solution.go new file mode 100644 index 00000000..bc5bd5a2 --- /dev/null +++ b/internal/puzzles/solutions/2016/day01/solution.go @@ -0,0 +1,40 @@ +package day01 + +import ( + "io" + + "github.com/obalunenko/advent-of-code/internal/puzzles" +) + +const ( + puzzleName = "day01" + year = "2016" +) + +type solution struct { + year string + name string +} + +func (s solution) Name() string { + return s.name +} + +func (s solution) Year() string { + return s.year +} + +func init() { + puzzles.Register(solution{ + year: year, + name: puzzleName, + }) +} + +func (s solution) Part1(input io.Reader) (string, error) { + return "", puzzles.ErrNotImplemented +} + +func (s solution) Part2(input io.Reader) (string, error) { + return "", puzzles.ErrNotImplemented +} diff --git a/internal/puzzles/solutions/2016/day01/solution_test.go b/internal/puzzles/solutions/2016/day01/solution_test.go new file mode 100644 index 00000000..5155fc99 --- /dev/null +++ b/internal/puzzles/solutions/2016/day01/solution_test.go @@ -0,0 +1,123 @@ +package day01 + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_solution_Part1(t *testing.T) { + type fields struct { + name string + } + + type args struct { + input io.Reader + } + + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "", + fields: fields{ + name: "", + }, + args: args{ + input: strings.NewReader("R2, L3"), + }, + want: "5", + wantErr: false, + }, + { + name: "", + fields: fields{ + name: "", + }, + args: args{ + input: strings.NewReader("R2, R2, R2"), + }, + want: "2", + wantErr: false, + }, + { + name: "", + fields: fields{ + name: "", + }, + args: args{ + input: strings.NewReader("R5, L5, R5, R3"), + }, + want: "12", + wantErr: false, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + s := solution{ + name: tt.fields.name, + } + + got, err := s.Part1(tt.args.input) + if tt.wantErr { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_solution_Part2(t *testing.T) { + t.Skip() + + type fields struct { + name string + } + + type args struct { + input io.Reader + } + + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + {}, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + s := solution{ + name: tt.fields.name, + } + + got, err := s.Part2(tt.args.input) + if tt.wantErr { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/puzzles/solutions/2016/day01/spec.md b/internal/puzzles/solutions/2016/day01/spec.md new file mode 100644 index 00000000..a8ca275e --- /dev/null +++ b/internal/puzzles/solutions/2016/day01/spec.md @@ -0,0 +1,28 @@ +# --- Day 1: No Time for a Taxicab --- + +Santa's sleigh uses a very high-precision clock to guide its movements, and the clock's oscillator +is regulated by stars. Unfortunately, the stars have been stolen... by the Easter Bunny. +To save Christmas, Santa needs you to retrieve all fifty stars by December 25th. + +Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; +the second puzzle is unlocked when you complete the first. Each puzzle grants one star. +Good luck! + +You're airdropped near Easter Bunny Headquarters in a city somewhere. +"Near", unfortunately, is as close as you can get - the instructions on the Easter Bunny Recruiting +Document the Elves intercepted start here, and nobody had time to work them out further. + +The Document indicates that you should start at the given coordinates (where you just landed) and face North. +Then, follow the provided sequence: either turn left (L) or right (R) 90 degrees, +then walk forward the given number of blocks, ending at a new intersection. + +There's no time to follow such ridiculous instructions on foot, though, +so you take a moment and work out the destination. Given that you can only walk on the street grid of the city, +how far is the shortest path to the destination? + +For example: + +- Following `R2, L3` leaves you `2` blocks East and `3` blocks North, or `5` blocks away. +- `R2, R2, R2` leaves you `2` blocks due South of your starting position, which is `2` blocks away. +- `R5, L5, R5, R3` leaves you `12` blocks away. +How many blocks away is Easter Bunny HQ? \ No newline at end of file From f8289c645a767ed3ce84f41a42e995e1f9f84544 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 14:16:41 +0300 Subject: [PATCH 2/7] feat(2016/day01): Implement first part --- cmd/aoc-cli/main.go | 1 - .../puzzles/solutions/2016/day01/solution.go | 185 +++++++++++++++++- .../solutions/2016/day01/solution_test.go | 1 + internal/puzzles/solutions/register.go | 5 +- 4 files changed, 188 insertions(+), 4 deletions(-) diff --git a/cmd/aoc-cli/main.go b/cmd/aoc-cli/main.go index 03706a5c..08669159 100644 --- a/cmd/aoc-cli/main.go +++ b/cmd/aoc-cli/main.go @@ -13,7 +13,6 @@ import ( "github.com/obalunenko/advent-of-code/internal/input" "github.com/obalunenko/advent-of-code/internal/puzzles" - // register all solutions. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions" ) diff --git a/internal/puzzles/solutions/2016/day01/solution.go b/internal/puzzles/solutions/2016/day01/solution.go index bc5bd5a2..5201aacf 100644 --- a/internal/puzzles/solutions/2016/day01/solution.go +++ b/internal/puzzles/solutions/2016/day01/solution.go @@ -1,7 +1,13 @@ package day01 import ( + "bytes" + "errors" + "fmt" "io" + "regexp" + "strconv" + "strings" "github.com/obalunenko/advent-of-code/internal/puzzles" ) @@ -32,9 +38,186 @@ func init() { } func (s solution) Part1(input io.Reader) (string, error) { - return "", puzzles.ErrNotImplemented + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(input); err != nil { + return "", fmt.Errorf("failed to read input: %w", err) + } + + c := newCab() + + cmds := strings.Split(buf.String(), ", ") + for _, cmd := range cmds { + t, s, err := splitCommand(cmd) + if err != nil { + return "", fmt.Errorf("split command: %w", err) + } + + if err = c.Move(t, s); err != nil { + return "", fmt.Errorf("move: %w", err) + } + } + + l := c.Pos().manhattan() + + return strconv.Itoa(l), nil } func (s solution) Part2(input io.Reader) (string, error) { return "", puzzles.ErrNotImplemented } + +type turn string + +const ( + leftTurn = "L" + rightTurn = "R" +) + +func tunrFromstring(s string) (turn, error) { + switch s { + case leftTurn: + return leftTurn, nil + case rightTurn: + return rightTurn, nil + default: + return "", errors.New("invalid turn value") + } +} + +type position struct { + x, y int +} + +func (p *position) addX(n int) { + p.x = p.x + n +} + +func (p *position) addY(n int) { + p.y = p.y + n +} + +func (p *position) subX(n int) { + p.x = p.x - n +} + +func (p *position) subY(n int) { + p.y = p.y - n +} + +func (p position) manhattan() int { + x, y := p.x, p.y + + if x < 0 { + x = -x + } + + if y < 0 { + y = -y + } + + return x + y +} + +type direction uint + +const ( + unknownDirection direction = iota + + northDirection + eastDirection + southDirection + westDirection + + sentinelDirection +) + +func (d direction) isValid() bool { + return d > unknownDirection && d < sentinelDirection +} + +func (d direction) strikeTo(t turn) direction { + switch t { + case rightTurn: + if d == westDirection { + return northDirection + } + + return d + 1 + case leftTurn: + if d == northDirection { + return westDirection + } + + return d - 1 + + default: + return unknownDirection + } +} + +type cab struct { + pos position + curDir direction +} + +func newCab() cab { + return cab{ + pos: position{ + x: 0, + y: 0, + }, + curDir: northDirection, + } +} +func (c *cab) Move(t turn, steps int) error { + c.curDir = c.curDir.strikeTo(t) + if !c.curDir.isValid() { + return errors.New("invalid direction") + } + + switch c.curDir { + case northDirection: + c.pos.addY(steps) + case eastDirection: + c.pos.addX(steps) + case southDirection: + c.pos.subY(steps) + case westDirection: + c.pos.subX(steps) + } + + return nil +} + +func (c cab) Pos() position { + return c.pos +} + +// Example: L4, R5 +var re = regexp.MustCompile(`(?msi)(L|R)(\d+)`) + +const ( + fullMatchPos = iota + turnPos + stepsPos + + totalMatchesNum = 3 +) + +func splitCommand(cmd string) (turn, int, error) { + parts := re.FindStringSubmatch(cmd) + if len(parts) != totalMatchesNum { + return "", 0, errors.New("invalid command") + } + + t, err := tunrFromstring(parts[turnPos]) + if err != nil { + return "", 0, errors.New("invalid turn") + } + s, err := strconv.Atoi(parts[stepsPos]) + if err != nil { + return "", 0, errors.New("invalid steps num") + } + + return t, s, nil +} diff --git a/internal/puzzles/solutions/2016/day01/solution_test.go b/internal/puzzles/solutions/2016/day01/solution_test.go index 5155fc99..52a0d75f 100644 --- a/internal/puzzles/solutions/2016/day01/solution_test.go +++ b/internal/puzzles/solutions/2016/day01/solution_test.go @@ -65,6 +65,7 @@ func Test_solution_Part1(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s := solution{ name: tt.fields.name, + year: year, } got, err := s.Part1(tt.args.input) diff --git a/internal/puzzles/solutions/register.go b/internal/puzzles/solutions/register.go index cc9c3d98..f2f7cc0e 100644 --- a/internal/puzzles/solutions/register.go +++ b/internal/puzzles/solutions/register.go @@ -4,7 +4,9 @@ import ( // 2015 solutions. // register day01 solution. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2015/day01" - + // 2016 solutions. + // register day01 solution. + _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2016/day01" // 2019 solutions. // register day01 solution. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2019/day01" @@ -14,7 +16,6 @@ import ( _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2019/day03" // register day04 solution. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2019/day04" - // 2020 solutions. // register day01 solution. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2020/day01" From a51267fe9ca9449c16c8039ebc4562d98862f480 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 15:54:07 +0300 Subject: [PATCH 3/7] refactor: Move input to puzzles; update coverage script --- .gitignore | 3 +- cmd/aoc-cli/main.go | 2 +- internal/{ => puzzles}/input/content.go | 0 .../{ => puzzles}/input/data/2015/day01.txt | 0 .../{ => puzzles}/input/data/2016/day01.txt | 0 .../{ => puzzles}/input/data/2019/day01.txt | 0 .../{ => puzzles}/input/data/2019/day02.txt | 0 .../{ => puzzles}/input/data/2019/day03.txt | 0 .../{ => puzzles}/input/data/2019/day04.txt | 0 .../{ => puzzles}/input/data/2020/day01.txt | 0 .../{ => puzzles}/input/data/2020/day02.txt | 0 scripts/coverage.sh | 38 ++++++++++--------- 12 files changed, 24 insertions(+), 19 deletions(-) rename internal/{ => puzzles}/input/content.go (100%) rename internal/{ => puzzles}/input/data/2015/day01.txt (100%) rename internal/{ => puzzles}/input/data/2016/day01.txt (100%) rename internal/{ => puzzles}/input/data/2019/day01.txt (100%) rename internal/{ => puzzles}/input/data/2019/day02.txt (100%) rename internal/{ => puzzles}/input/data/2019/day03.txt (100%) rename internal/{ => puzzles}/input/data/2019/day04.txt (100%) rename internal/{ => puzzles}/input/data/2020/day01.txt (100%) rename internal/{ => puzzles}/input/data/2020/day02.txt (100%) diff --git a/.gitignore b/.gitignore index 37d76c98..e081f134 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ bin/ dist/ release/ -.DS_Store \ No newline at end of file +.DS_Store +coverage/ \ No newline at end of file diff --git a/cmd/aoc-cli/main.go b/cmd/aoc-cli/main.go index 08669159..b92e0641 100644 --- a/cmd/aoc-cli/main.go +++ b/cmd/aoc-cli/main.go @@ -11,8 +11,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/urfave/cli" - "github.com/obalunenko/advent-of-code/internal/input" "github.com/obalunenko/advent-of-code/internal/puzzles" + "github.com/obalunenko/advent-of-code/internal/puzzles/input" // register all solutions. _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions" ) diff --git a/internal/input/content.go b/internal/puzzles/input/content.go similarity index 100% rename from internal/input/content.go rename to internal/puzzles/input/content.go diff --git a/internal/input/data/2015/day01.txt b/internal/puzzles/input/data/2015/day01.txt similarity index 100% rename from internal/input/data/2015/day01.txt rename to internal/puzzles/input/data/2015/day01.txt diff --git a/internal/input/data/2016/day01.txt b/internal/puzzles/input/data/2016/day01.txt similarity index 100% rename from internal/input/data/2016/day01.txt rename to internal/puzzles/input/data/2016/day01.txt diff --git a/internal/input/data/2019/day01.txt b/internal/puzzles/input/data/2019/day01.txt similarity index 100% rename from internal/input/data/2019/day01.txt rename to internal/puzzles/input/data/2019/day01.txt diff --git a/internal/input/data/2019/day02.txt b/internal/puzzles/input/data/2019/day02.txt similarity index 100% rename from internal/input/data/2019/day02.txt rename to internal/puzzles/input/data/2019/day02.txt diff --git a/internal/input/data/2019/day03.txt b/internal/puzzles/input/data/2019/day03.txt similarity index 100% rename from internal/input/data/2019/day03.txt rename to internal/puzzles/input/data/2019/day03.txt diff --git a/internal/input/data/2019/day04.txt b/internal/puzzles/input/data/2019/day04.txt similarity index 100% rename from internal/input/data/2019/day04.txt rename to internal/puzzles/input/data/2019/day04.txt diff --git a/internal/input/data/2020/day01.txt b/internal/puzzles/input/data/2020/day01.txt similarity index 100% rename from internal/input/data/2020/day01.txt rename to internal/puzzles/input/data/2020/day01.txt diff --git a/internal/input/data/2020/day02.txt b/internal/puzzles/input/data/2020/day02.txt similarity index 100% rename from internal/input/data/2020/day02.txt rename to internal/puzzles/input/data/2020/day02.txt diff --git a/scripts/coverage.sh b/scripts/coverage.sh index e6a7b773..a9f47ae9 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -1,25 +1,29 @@ -#!/usr/bin/env bash +#!/bin/sh -set -Eeuo pipefail +set -eu -function cleanup() { - trap - SIGINT SIGTERM ERR EXIT - echo "cleanup running" - rm -rf coverage.out.tmp -} +SCRIPT_NAME="$(basename "$0")" -trap cleanup SIGINT SIGTERM ERR EXIT +echo "${SCRIPT_NAME} is running... " -SCRIPT_NAME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")" +rm -rf coverage +mkdir -p coverage -echo "${SCRIPT_NAME} is running... " +# go test --count=1 -tags=integration_test -coverprofile ./coverage/integration.cov -covermode=atomic ./... +go test --count=1 -coverprofile ./coverage/unit.cov -covermode=atomic ./... + + +{ +echo "mode: atomic" +tail -q -n +2 ./coverage/*.cov +} >> ./coverage/full.cov +gocov convert ./coverage/full.cov > ./coverage/full.json +gocov report ./coverage/full.json +gocov-html ./coverage/full.json >./coverage/full.html +cp ./coverage/full.cov coverage.out +# open ./coverage/full.html -go test -race -coverpkg=./... -coverprofile coverage.out.tmp ./... +# go tool cover -html=./coverage/full.cov -# shellcheck disable=SC2002 -cat coverage.out.tmp | grep -v "cmd/" >coverage.out -gocov convert coverage.out >coverage.out.json -gocov report coverage.out.json -gocov-html coverage.out.json > coverage.out.html -go tool cover -html=coverage.out +echo "${SCRIPT_NAME} done." From 3ff5e7c63b8c238380becc361925646d3a3b304b Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 16:25:17 +0300 Subject: [PATCH 4/7] chore(scripts): Update coverage script --- .github/workflows/test-build.yml | 2 +- scripts/coverage.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 23ebdd60..725c77ec 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -69,7 +69,7 @@ jobs: - name: Cover report if: success() run: | - bash <(curl -s https://codecov.io/bash) + bash <(curl -s https://codecov.io/bash) -f ./coverage/full.cov - name: Install GoReleaser uses: goreleaser/goreleaser-action@v2 diff --git a/scripts/coverage.sh b/scripts/coverage.sh index a9f47ae9..80b5e8a5 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -21,7 +21,6 @@ tail -q -n +2 ./coverage/*.cov gocov convert ./coverage/full.cov > ./coverage/full.json gocov report ./coverage/full.json gocov-html ./coverage/full.json >./coverage/full.html -cp ./coverage/full.cov coverage.out # open ./coverage/full.html # go tool cover -html=./coverage/full.cov From c0a35c5e21c738faef4392531656d41c7c66f4b4 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 18:17:12 +0300 Subject: [PATCH 5/7] feat(2016/day01): Implement part2 --- .../puzzles/solutions/2016/day01/solution.go | 204 ++++++++++++++++-- .../solutions/2016/day01/solution_test.go | 14 +- internal/puzzles/solutions/2016/day01/spec.md | 15 +- 3 files changed, 208 insertions(+), 25 deletions(-) diff --git a/internal/puzzles/solutions/2016/day01/solution.go b/internal/puzzles/solutions/2016/day01/solution.go index 5201aacf..cfc42722 100644 --- a/internal/puzzles/solutions/2016/day01/solution.go +++ b/internal/puzzles/solutions/2016/day01/solution.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/obalunenko/advent-of-code/internal/puzzles" ) @@ -45,6 +46,10 @@ func (s solution) Part1(input io.Reader) (string, error) { c := newCab() + go func() { + c.n.start() + }() + cmds := strings.Split(buf.String(), ", ") for _, cmd := range cmds { t, s, err := splitCommand(cmd) @@ -57,13 +62,46 @@ func (s solution) Part1(input io.Reader) (string, error) { } } - l := c.Pos().manhattan() + c.n.stop() + + l := c.n.Pos().manhattan() return strconv.Itoa(l), nil } func (s solution) Part2(input io.Reader) (string, error) { - return "", puzzles.ErrNotImplemented + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(input); err != nil { + return "", fmt.Errorf("failed to read input: %w", err) + } + + c := newCab() + + go c.n.start() + + cmds := strings.Split(buf.String(), ", ") + for _, cmd := range cmds { + t, s, err := splitCommand(cmd) + if err != nil { + return "", fmt.Errorf("split command: %w", err) + } + + if err = c.Move(t, s); err != nil { + return "", fmt.Errorf("move: %w", err) + } + } + + c.n.stop() + + rl := c.n.revisitedList() + if len(rl) == 0 { + return "", errors.New("no revisited points") + } + + // get first + l := rl[0].manhattan() + + return strconv.Itoa(l), nil } type turn string @@ -73,14 +111,16 @@ const ( rightTurn = "R" ) -func tunrFromstring(s string) (turn, error) { +var errInvalidTurn = errors.New("invalid turn value") + +func turnFromstring(s string) (turn, error) { switch s { case leftTurn: return leftTurn, nil case rightTurn: return rightTurn, nil default: - return "", errors.New("invalid turn value") + return "", errInvalidTurn } } @@ -156,41 +196,92 @@ func (d direction) strikeTo(t turn) direction { } type cab struct { - pos position curDir direction + n navigator } func newCab() cab { return cab{ - pos: position{ - x: 0, - y: 0, - }, curDir: northDirection, + n: newNavigator(), } } + +var errInvalidDirect = errors.New("invalid direction") + +const ( + step = 1 +) + func (c *cab) Move(t turn, steps int) error { c.curDir = c.curDir.strikeTo(t) if !c.curDir.isValid() { - return errors.New("invalid direction") + return errInvalidDirect } switch c.curDir { case northDirection: - c.pos.addY(steps) + c.n.moveNorth(steps) case eastDirection: - c.pos.addX(steps) + c.n.moveEast(steps) case southDirection: - c.pos.subY(steps) + c.n.moveSouth(steps) case westDirection: - c.pos.subX(steps) + c.n.moveWest(steps) } return nil } -func (c cab) Pos() position { - return c.pos +func (n *navigator) moveNorth(steps int) { + for i := 0; i < steps; i++ { + n.mu.Lock() + n.pos.addY(step) + n.mu.Unlock() + + n.record <- n.Pos() + } +} + +func (n *navigator) moveEast(steps int) { + for i := 0; i < steps; i++ { + n.mu.Lock() + n.pos.addX(step) + n.mu.Unlock() + + n.record <- n.Pos() + } +} + +func (n *navigator) moveSouth(steps int) { + for i := 0; i < steps; i++ { + n.mu.Lock() + n.pos.subY(step) + n.mu.Unlock() + + n.record <- n.Pos() + } +} + +func (n *navigator) moveWest(steps int) { + for i := 0; i < steps; i++ { + n.mu.Lock() + n.pos.subX(step) + n.mu.Unlock() + + n.record <- n.Pos() + } +} + +func (c *cab) Track() track { + return c.n.track +} + +func (n navigator) Pos() position { + n.mu.Lock() + defer n.mu.Unlock() + + return n.pos } // Example: L4, R5 @@ -204,20 +295,93 @@ const ( totalMatchesNum = 3 ) +var errInvalidCMD = errors.New("invalid command") + func splitCommand(cmd string) (turn, int, error) { parts := re.FindStringSubmatch(cmd) if len(parts) != totalMatchesNum { - return "", 0, errors.New("invalid command") + return "", 0, errInvalidCMD } - t, err := tunrFromstring(parts[turnPos]) + t, err := turnFromstring(parts[turnPos]) if err != nil { - return "", 0, errors.New("invalid turn") + return "", 0, fmt.Errorf("turnFromstring: %w", err) } s, err := strconv.Atoi(parts[stepsPos]) if err != nil { - return "", 0, errors.New("invalid steps num") + return "", 0, fmt.Errorf("invalid steps num: %w", err) } return t, s, nil } + +type navigator struct { + record chan position + pos position + track track + mu *sync.Mutex + wg *sync.WaitGroup + revisited []position +} + +func (n *navigator) recordTrack(p position) { + n.mu.Lock() + + defer func() { + n.mu.Unlock() + }() + + if n.track.isVisited(p) { + n.revisited = append(n.revisited, p) + } + + n.track.record(p) +} + +func (n *navigator) start() { + n.wg.Add(1) + + for p := range n.record { + n.recordTrack(p) + } + + n.wg.Done() +} + +func (n *navigator) stop() { + close(n.record) + + n.wg.Wait() +} + +func (n navigator) revisitedList() []position { + return n.revisited +} + +func newNavigator() navigator { + return navigator{ + record: make(chan position), + pos: position{ + x: 0, + y: 0, + }, + track: newTrack(), + mu: &sync.Mutex{}, + wg: &sync.WaitGroup{}, + revisited: []position{}, + } +} + +type track map[position]bool + +func newTrack() track { + return make(track) +} + +func (t track) record(p position) { + t[p] = true +} + +func (t track) isVisited(p position) bool { + return t[p] +} diff --git a/internal/puzzles/solutions/2016/day01/solution_test.go b/internal/puzzles/solutions/2016/day01/solution_test.go index 52a0d75f..66d48584 100644 --- a/internal/puzzles/solutions/2016/day01/solution_test.go +++ b/internal/puzzles/solutions/2016/day01/solution_test.go @@ -82,8 +82,6 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - t.Skip() - type fields struct { name string } @@ -99,7 +97,17 @@ func Test_solution_Part2(t *testing.T) { want string wantErr bool }{ - {}, + { + name: "", + fields: fields{ + name: "", + }, + args: args{ + input: strings.NewReader("R8, R4, R4, R8"), + }, + want: "4", + wantErr: false, + }, } for _, tt := range tests { diff --git a/internal/puzzles/solutions/2016/day01/spec.md b/internal/puzzles/solutions/2016/day01/spec.md index a8ca275e..8bc64227 100644 --- a/internal/puzzles/solutions/2016/day01/spec.md +++ b/internal/puzzles/solutions/2016/day01/spec.md @@ -1,5 +1,7 @@ # --- Day 1: No Time for a Taxicab --- +## Part One + Santa's sleigh uses a very high-precision clock to guide its movements, and the clock's oscillator is regulated by stars. Unfortunately, the stars have been stolen... by the Easter Bunny. To save Christmas, Santa needs you to retrieve all fifty stars by December 25th. @@ -13,7 +15,7 @@ You're airdropped near Easter Bunny Headquarters in a city somewhere. Document the Elves intercepted start here, and nobody had time to work them out further. The Document indicates that you should start at the given coordinates (where you just landed) and face North. -Then, follow the provided sequence: either turn left (L) or right (R) 90 degrees, +Then, follow the provided sequence: either turn left `(L)` or right `(R)` 90 degrees, then walk forward the given number of blocks, ending at a new intersection. There's no time to follow such ridiculous instructions on foot, though, @@ -25,4 +27,13 @@ For example: - Following `R2, L3` leaves you `2` blocks East and `3` blocks North, or `5` blocks away. - `R2, R2, R2` leaves you `2` blocks due South of your starting position, which is `2` blocks away. - `R5, L5, R5, R3` leaves you `12` blocks away. -How many blocks away is Easter Bunny HQ? \ No newline at end of file +How many blocks away is Easter Bunny HQ? + +## Part Two + +Then, you notice the instructions continue on the back of the Recruiting Document. Easter Bunny HQ is actually at the first +location you visit twice. + +For example, if your instructions are `R8, R4, R4, R8`, the first location you visit twice is `4` blocks away, due `East`. + +How many blocks away is the first location you visit twice? \ No newline at end of file From aaac198c8a2950bcdf49bf584b768788411d492f Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 18:17:46 +0300 Subject: [PATCH 6/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc015b71..51b47c27 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ This repository contains solutions for puzzles and cli tool to run solutions to
2016 - - [ ] [Day 1: No Time for a Taxicab](https://adventofcode.com/2016/day/1) + - [x] [Day 1: No Time for a Taxicab](https://adventofcode.com/2016/day/1) - [ ] [Day 2: Bathroom Security](https://adventofcode.com/2016/day/2) - [ ] [Day 3: Squares With Three Sides](https://adventofcode.com/2016/day/3) - [ ] [Day 4: Security Through Obscurity](https://adventofcode.com/2016/day/4) From 111b10aaf8d883872475e11086783c06f1372f80 Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Wed, 23 Jun 2021 18:21:22 +0300 Subject: [PATCH 7/7] Update sonar-project.properties --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index c4eadac9..6ef8db72 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -15,5 +15,5 @@ sonar.exclusions=**/*_test.go,**/vendor/** sonar.tests=. sonar.test.inclusions=**/*_test.go sonar.go.tests.reportPaths=tests.out -sonar.go.coverage.reportPaths=coverage.out +sonar.go.coverage.reportPaths=./coverage/full.cov sonar.go.golangci-lint.reportPaths=linters.out \ No newline at end of file