diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ee2715f3..56b2500d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -65,7 +65,7 @@ jobs: os: [ 'ubuntu-20.04' ] go: [ '1.17' ] runs-on: ${{ matrix.os }} - name: Send cover and quality reports + name: Quality reports steps: - name: Checkout uses: actions/checkout@v2.4.0 @@ -87,13 +87,21 @@ jobs: - name: Prepare test coverage run: | make test-cover + + - name: Tests report + run: | + make test-sonar-report + + - name: Prepare lint report + run: | + make lint-sonar - - name: Cover report + - name: Cover report upload if: success() run: | bash <(curl -s https://codecov.io/bash) -f ./coverage/full.cov - - name: SonarCloud Scan report + - name: SonarCloud report upload uses: sonarsource/sonarcloud-github-action@v1.6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e467d1ba..a14e0375 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ bin/ dist/ .DS_Store -coverage/ \ No newline at end of file +coverage/ +tests-report.json \ No newline at end of file diff --git a/.golangci.pipe.yml b/.golangci.pipe.yml index 2d3d104c..cfa17a1e 100644 --- a/.golangci.pipe.yml +++ b/.golangci.pipe.yml @@ -13,7 +13,7 @@ linters-settings: gofmt: simplify: true goimports: - local-prefixes: github.com/obalunenko/advent-of-code + local-prefixes: github.com/obalunenko/advent-of-code/ revive: # see https://github.com/mgechev/revive#available-rules for details. ignore-generated-header: true @@ -27,16 +27,6 @@ linters-settings: severity: warning arguments: [ [ "call-chain", "loop", "method-call", "recover", "return" ] ] -run: - issues-exit-code: 1 - tests: true - skip-dirs: - - vendor/ - skip-files: - - \.pb\.go$ - out-format: - - github-actions - issues: exclude-use-default: false exclude: @@ -64,3 +54,73 @@ issues: - gofumpt - goimports - gosimple + - path: internal/puzzles/constants.go + linters: + - revive + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: true + + # Fix found issues (if it's supported by the linter) + fix: false + + severity: + # Default value is empty string. + # Set the default severity for issues. If severity rules are defined and the issues + # do not match or no severity is provided to the rule this will be the default + # severity applied. Severities should match the supported severity names of the + # selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + default-severity: error + + # The default value is false. + # If set to true severity-rules regular expressions become case sensitive. + case-sensitive: false + + # Default value is empty list. + # When a list of severity rules are provided, severity information will be added to lint + # issues. Severity rules have the same filtering capability as exclude rules except you + # are allowed to specify one matcher per severity rule. + # Only affects out formats that support setting severity information. + rules: + - linters: + - dupl + severity: warning + +run: + issues-exit-code: 1 + tests: true + skip-dirs: + - vendor/ + skip-files: + - \.pb\.go$ + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions + # default is "colored-line-number" + format: github-actions + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + + # make issues output unique by line, default is true + uniq-by-line: true + + # add a prefix to the output file references; default is no prefix + path-prefix: "" + + # sorts results by: filepath, line and column + sort-results: true + + diff --git a/.golangci.yml b/.golangci.yml index d47e98ce..7f76f2fb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,113 +1,118 @@ -linters: - enable-all: true - disable: - - gochecknoglobals - - gochecknoinits - - godot - - paralleltest - - gci - - gofumpt - linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: true - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: true - - gofmt: - simplify: true - govet: - check-shadowing: true - golint: - min-confidence: 0 - gocyclo: - min-complexity: 10 - maligned: - suggest-new: true + depguard: + list-type: blacklist + packages: + # logging is allowed only by logutils.Log, logrus + # is allowed to use only in logutils package + - github.com/sirupsen/logrus + packages-with-error-message: + - github.com/sirupsen/logrus: "logging is allowed only by github.com/obalunenko/logger" dupl: threshold: 100 + funlen: + lines: 100 + statements: 50 + gci: + local-prefixes: github.com/obalunenko/advent-of-code goconst: min-len: 2 min-occurrences: 2 - misspell: - locale: US - lll: - line-length: 120 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - hugeParam + - octalLiteral + - wrapperFunc + gocyclo: + min-complexity: 15 goimports: - local-prefixes: github.com/obalunenko/advent-of-code/ - unparam: - algo: cha - check-exported: false - prealloc: - simple: true - range-loops: true # Report preallocation suggestions on range loops, true by default - for-loops: false # Report preallocation suggestions on for loops, false by default + local-prefixes: github.com/obalunenko/advent-of-code gomnd: settings: mnd: - # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. - checks: [case,condition,operation,return] - gocritic: - enabled-checks: - - docStub - - rangeValCopy - - yodaStyleExpr - - appendAssign - - appendCombine - - caseOrder - - badCond - - commentedOutCode - - commentFormatting - - commentedOutImport - - dupArg - - dupBranchBody - - elseif - - emptyStringTest - - indexAlloc - - initClause - - captlocal - - weakCond - - deprecatedComment - - flagDeref - - flagName - - hugeParam - - ifElseChain - - nilValReturn - - rangeExprCopy - - ptrToRefParam - - underef - - unnecessaryBlock - - valSwap - settings: # settings passed to gocritic - captLocal: # must be valid enabled check name - paramsOnly: true - rangeValCopy: - sizeThreshold: 320 - hugeParam: - sizeThreshold: 500 - rangeExprCopy: - skipTestFuncs: true - underef: - skipRecvDeref: true - -run: - skip-dirs: - - vendor/ - - internal/input/ - issues-exit-code: 0 - tests: true - skip-files: - - \.pb\.go$ + # don't include the "operation" and "assign" + checks: [ argument,case,condition,return ] + govet: + check-shadowing: true + lll: + line-length: 140 + misspell: + locale: US + nolintlint: + allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) + allow-unused: false # report any unused nolint directives + require-explanation: false # don't require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exportloopref + - exhaustive + - funlen + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - noctx + - nolintlint + - rowserrcheck + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + - revive + - wsl -output: - format: colored-line-number + # don't enable: + # - asciicheck + # - scopelint + # - gochecknoglobals + # - gocognit + # - godot + # - godox + # - goerr113 + # - interfacer + # - maligned + # - nestif + # - prealloc + # - testpackage issues: + exclude-use-default: false + exclude: + # for "public interface + private struct implementation" cases only! + - exported func * returns unexported type *, which can be annoying to use + - should have a package comment, unless it's in another file for this package # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: # Exclude some linters from running on tests files. @@ -129,4 +134,74 @@ issues: - gofumpt - goimports - gosimple + - path: internal/puzzles/constants.go + linters: + - revive + - path: internal/puzzles/solutions/ + linters: + - gochecknoinits + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: true + + # Fix found issues (if it's supported by the linter) + fix: false + + severity: + # Default value is empty string. + # Set the default severity for issues. If severity rules are defined and the issues + # do not match or no severity is provided to the rule this will be the default + # severity applied. Severities should match the supported severity names of the + # selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + default-severity: error + + # The default value is false. + # If set to true severity-rules regular expressions become case sensitive. + case-sensitive: false + + # Default value is empty list. + # When a list of severity rules are provided, severity information will be added to lint + # issues. Severity rules have the same filtering capability as exclude rules except you + # are allowed to specify one matcher per severity rule. + # Only affects out formats that support setting severity information. + rules: + - linters: + - dupl + severity: warning + +run: + issues-exit-code: 0 + tests: true + skip-dirs: + - vendor/ + skip-files: + - \.pb\.go$ + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions + # default is "colored-line-number" + format: checkstyle + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + + # make issues output unique by line, default is true + uniq-by-line: true + + # add a prefix to the output file references; default is no prefix + path-prefix: "" + # sorts results by: filepath, line and column + sort-results: true diff --git a/Makefile b/Makefile index 5e328fba..4fa4800b 100644 --- a/Makefile +++ b/Makefile @@ -35,10 +35,14 @@ compile-aoc-cli: ## Test coverage report. test-cover: - ${call colored, test-cover is running...} ./scripts/tests/coverage.sh .PHONY: test-cover +## Tests sonar report generate. +test-sonar-report: + ./scripts/tests/sonar-report.sh +.PHONY: test-sonar-report + ## Open coverage report. open-cover-report: test-cover ./scripts/open-coverage-report.sh @@ -67,7 +71,6 @@ imports: ## Format code with go fmt. fmt: - ${call colored, fmt is running...} ./scripts/style/fmt.sh .PHONY: fmt @@ -81,7 +84,6 @@ install-tools: ## vet project vet: - ${call colored, vet is running...} ./scripts/linting/run-vet.sh .PHONY: vet @@ -92,12 +94,16 @@ lint-full: ## Run linting for build pipeline lint-pipeline: - ./scripts/linting/run-linters-pipeline.sh + ./scripts/linting/golangci-pipeline.sh .PHONY: lint-pipeline -## recreate all generated code and swagger documentation. +## Run linting for sonar report +lint-sonar: + ./scripts/linting/golangci-sonar.sh +.PHONY: lint-sonar + +## recreate all generated code and documentation. codegen: - ${call colored, codegen is running...} ./scripts/codegen/go-generate.sh .PHONY: codegen @@ -112,7 +118,6 @@ release: ## Release local snapshot release-local-snapshot: - ${call colored, release is running...} ./scripts/release/local-snapshot-release.sh .PHONY: release-local-snapshot diff --git a/cmd/aoc-cli/main.go b/cmd/aoc-cli/main.go index 5261915b..f79b9bab 100644 --- a/cmd/aoc-cli/main.go +++ b/cmd/aoc-cli/main.go @@ -16,8 +16,7 @@ import ( "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" + _ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions" // register all solutions. ) const ( @@ -116,7 +115,7 @@ func handleYearChoices(ctx context.Context, opt promptui.Select) error { } func menuPuzzle(ctx context.Context, year string) error { - solvers := puzzles.NamesByYear(year) + solvers := puzzles.DaysByYear(year) prompt := promptui.Select{ Label: "Puzzles menu (exit' for exit; back - to return to year selection)", @@ -168,25 +167,25 @@ func handlePuzzleChoices(ctx context.Context, year string, opt promptui.Select) } } -func isExit(input string) bool { - return strings.EqualFold(exit, input) +func isExit(in string) bool { + return strings.EqualFold(exit, in) } func isAbort(err error) bool { return strings.HasSuffix(err.Error(), abort) } -func isBack(input string) bool { - return strings.EqualFold(back, input) +func isBack(in string) bool { + return strings.EqualFold(back, in) } -func run(year string, name string) (puzzles.Result, error) { - s, err := puzzles.GetSolver(year, name) +func run(year, day string) (puzzles.Result, error) { + s, err := puzzles.GetSolver(year, day) if err != nil { return puzzles.Result{}, fmt.Errorf("failed to get solver: %w", err) } - fullName, err := puzzles.MakeName(s.Year(), s.Name()) + fullName, err := puzzles.MakeName(s.Year(), s.Day()) if err != nil { return puzzles.Result{}, fmt.Errorf("failed to make full name: %w", err) } @@ -198,7 +197,7 @@ func run(year string, name string) (puzzles.Result, error) { res, err := puzzles.Run(s, bytes.NewReader(asset)) if err != nil { - return puzzles.Result{}, fmt.Errorf("failed to run [%s]: %w", s.Name(), err) + return puzzles.Result{}, fmt.Errorf("failed to run [%s]: %w", fullName, err) } return res, nil diff --git a/cmd/aoc-cli/main_test.go b/cmd/aoc-cli/main_test.go new file mode 100644 index 00000000..a315e184 --- /dev/null +++ b/cmd/aoc-cli/main_test.go @@ -0,0 +1,2232 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/obalunenko/advent-of-code/internal/puzzles" +) + +type args struct { + year string + name string +} + +type testcase struct { + name string + args args + want puzzles.Result + wantErr bool +} + +// Regression tests for all puzzles. Check that answers still correct. +func Test_run(t *testing.T) { + var tests []testcase + + tests = append(tests, invalid()...) + tests = append(tests, testcases2015()...) + tests = append(tests, testcases2016()...) + tests = append(tests, testcases2017()...) + tests = append(tests, testcases2018()...) + tests = append(tests, testcases2019()...) + tests = append(tests, testcases2020()...) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := run(tt.args.year, tt.args.name) + if tt.wantErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func invalid() []testcase { + return []testcase{ + { + name: "empty year", + args: args{ + year: "", + name: puzzles.Day01.String(), + }, + want: puzzles.Result{}, + wantErr: true, + }, + { + name: "empty day", + args: args{ + year: puzzles.Year2016.String(), + name: "", + }, + want: puzzles.Result{}, + wantErr: true, + }, + { + name: "not exist day", + args: args{ + year: puzzles.Year2016.String(), + name: "daynotexist", + }, + want: puzzles.Result{}, + wantErr: true, + }, + { + name: "not exist year", + args: args{ + year: "notexist", + name: puzzles.Day01.String(), + }, + want: puzzles.Result{}, + wantErr: true, + }, + } +} + +func testcases2015() []testcase { + year := puzzles.Year2015.String() + + return []testcase{ + { + name: "2015/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "232", + Part2: "1783", + }, + wantErr: false, + }, + { + name: "2015/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2015/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} + +func testcases2016() []testcase { + year := puzzles.Year2016.String() + + return []testcase{ + { + name: "2016/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "307", + Part2: "165", + }, + wantErr: false, + }, + { + name: "2016/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2016/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} + +func testcases2017() []testcase { + year := puzzles.Year2017.String() + + return []testcase{ + { + name: "2017/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "1029", + Part2: "1220", + }, + wantErr: false, + }, + { + name: "2017/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2017/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} + +func testcases2018() []testcase { + year := puzzles.Year2018.String() + + return []testcase{ + { + name: "2018/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "439", + Part2: "124645", + }, + wantErr: false, + }, + { + name: "2018/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2018/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} + +func testcases2019() []testcase { + year := puzzles.Year2019.String() + + return []testcase{ + { + name: "2019/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "3464458", + Part2: "5193796", + }, + wantErr: false, + }, + { + name: "2019/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "2890696", + Part2: "8226", + }, + wantErr: false, + }, + { + name: "2019/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "1195", + Part2: "91518", + }, + wantErr: false, + }, + { + name: "2019/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "2779", + Part2: "1972", + }, + wantErr: false, + }, + { + name: "2019/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2019/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} + +func testcases2020() []testcase { + year := puzzles.Year2020.String() + + return []testcase{ + { + name: "2020/day01", + args: args{ + year: year, + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day01.String(), + Part1: "270144", + Part2: "261342720", + }, + wantErr: false, + }, + { + name: "2020/day02", + args: args{ + year: year, + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day02.String(), + Part1: "456", + Part2: "308", + }, + wantErr: false, + }, + { + name: "2020/day03", + args: args{ + year: year, + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day04", + args: args{ + year: year, + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day05", + args: args{ + year: year, + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day06", + args: args{ + year: year, + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day07", + args: args{ + year: year, + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day08", + args: args{ + year: year, + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day09", + args: args{ + year: year, + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day10", + args: args{ + year: year, + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day11", + args: args{ + year: year, + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day12", + args: args{ + year: year, + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day13", + args: args{ + year: year, + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day14", + args: args{ + year: year, + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day15", + args: args{ + year: year, + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day16", + args: args{ + year: year, + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day17", + args: args{ + year: year, + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day18", + args: args{ + year: year, + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day19", + args: args{ + year: year, + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day20", + args: args{ + year: year, + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day21", + args: args{ + year: year, + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day22", + args: args{ + year: year, + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day23", + args: args{ + year: year, + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day24", + args: args{ + year: year, + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: "2020/day25", + args: args{ + year: year, + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year, + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} diff --git a/internal/puzzles/constants.go b/internal/puzzles/constants.go new file mode 100644 index 00000000..6d44ad34 --- /dev/null +++ b/internal/puzzles/constants.go @@ -0,0 +1,56 @@ +package puzzles + +//go:generate stringer --type=Day --trimprefix=true --linecomment=true + +// Day presents here for purpose of documentation. +type Day int + +const ( + dayUnknown Day = iota + + Day01 // day01 + Day02 // day02 + Day03 // day03 + Day04 // day04 + Day05 // day05 + Day06 // day06 + Day07 // day07 + Day08 // day08 + Day09 // day09 + Day10 // day10 + Day11 // day11 + Day12 // day12 + Day13 // day13 + Day14 // day14 + Day15 // day15 + Day16 // day16 + Day17 // day17 + Day18 // day18 + Day19 // day19 + Day20 // day20 + Day21 // day21 + Day22 // day22 + Day23 // day23 + Day24 // day24 + Day25 // day25 + + daySentinel +) + +//go:generate stringer --type=Year --trimprefix=true --linecomment=true + +// Year presents here for purpose of documentation. +type Year int + +const ( + yearUnknown Year = iota + + Year2015 // 2015 + Year2016 // 2016 + Year2017 // 2017 + Year2018 // 2018 + Year2019 // 2019 + Year2020 // 2020 + + yearSentinel +) diff --git a/internal/puzzles/day_string.go b/internal/puzzles/day_string.go new file mode 100644 index 00000000..ff8aaf58 --- /dev/null +++ b/internal/puzzles/day_string.go @@ -0,0 +1,49 @@ +// Code generated by "stringer --type=Day --trimprefix=true --linecomment=true"; DO NOT EDIT. + +package puzzles + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[dayUnknown-0] + _ = x[Day01-1] + _ = x[Day02-2] + _ = x[Day03-3] + _ = x[Day04-4] + _ = x[Day05-5] + _ = x[Day06-6] + _ = x[Day07-7] + _ = x[Day08-8] + _ = x[Day09-9] + _ = x[Day10-10] + _ = x[Day11-11] + _ = x[Day12-12] + _ = x[Day13-13] + _ = x[Day14-14] + _ = x[Day15-15] + _ = x[Day16-16] + _ = x[Day17-17] + _ = x[Day18-18] + _ = x[Day19-19] + _ = x[Day20-20] + _ = x[Day21-21] + _ = x[Day22-22] + _ = x[Day23-23] + _ = x[Day24-24] + _ = x[Day25-25] + _ = x[daySentinel-26] +} + +const _Day_name = "dayUnknownday01day02day03day04day05day06day07day08day09day10day11day12day13day14day15day16day17day18day19day20day21day22day23day24day25daySentinel" + +var _Day_index = [...]uint8{0, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 146} + +func (i Day) String() string { + if i < 0 || i >= Day(len(_Day_index)-1) { + return "Day(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Day_name[_Day_index[i]:_Day_index[i+1]] +} diff --git a/internal/puzzles/day_string_test.go b/internal/puzzles/day_string_test.go new file mode 100644 index 00000000..ee167801 --- /dev/null +++ b/internal/puzzles/day_string_test.go @@ -0,0 +1,35 @@ +package puzzles + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDay_String(t *testing.T) { + const dayNotExist Day = 99 + + var tests = []struct { + name string + i Day + want string + }{ + { + name: "exist", + i: Day01, + want: "day01", + }, + { + name: "not exist", + i: dayNotExist, + want: "Day(99)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.i.String() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/puzzles/input/content.go b/internal/puzzles/input/content.go index 3edb881c..7a57e4d8 100644 --- a/internal/puzzles/input/content.go +++ b/internal/puzzles/input/content.go @@ -1,3 +1,4 @@ +// Package input provides access to embedded puzzles input files. package input import ( @@ -21,6 +22,9 @@ func Asset(name string) ([]byte, error) { filepath.Join(dir, name))) } +// MustAsset loads and returns the asset for the given name. +// It panics if the asset could not be found or +// could not be loaded. func MustAsset(name string) []byte { res, err := Asset(name) if err != nil { diff --git a/internal/puzzles/input/data/2016/day01.txt b/internal/puzzles/input/data/2016/day01.txt index 0c1b018b..205f97b1 100644 --- a/internal/puzzles/input/data/2016/day01.txt +++ b/internal/puzzles/input/data/2016/day01.txt @@ -1 +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 +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 diff --git a/internal/puzzles/input/data/2017/day01.txt b/internal/puzzles/input/data/2017/day01.txt index fd0f01bf..c4dcb38a 100644 --- a/internal/puzzles/input/data/2017/day01.txt +++ b/internal/puzzles/input/data/2017/day01.txt @@ -1 +1 @@ -6592822488931338589815525425236818285229555616392928433262436847386544514648645288129834834862363847542262953164877694234514375164927616649264122487182321437459646851966649732474925353281699895326824852555747127547527163197544539468632369858413232684269835288817735678173986264554586412678364433327621627496939956645283712453265255261565511586373551439198276373843771249563722914847255524452675842558622845416218195374459386785618255129831539984559644185369543662821311686162137672168266152494656448824719791398797359326412235723234585539515385352426579831251943911197862994974133738196775618715739412713224837531544346114877971977411275354168752719858889347588136787894798476123335894514342411742111135337286449968879251481449757294167363867119927811513529711239534914119292833111624483472466781475951494348516125474142532923858941279569675445694654355314925386833175795464912974865287564866767924677333599828829875283753669783176288899797691713766199641716546284841387455733132519649365113182432238477673375234793394595435816924453585513973119548841577126141962776649294322189695375451743747581241922657947182232454611837512564776273929815169367899818698892234618847815155578736875295629917247977658723868641411493551796998791839776335793682643551875947346347344695869874564432566956882395424267187552799458352121248147371938943799995158617871393289534789214852747976587432857675156884837634687257363975437535621197887877326295229195663235129213398178282549432599455965759999159247295857366485345759516622427833518837458236123723353817444545271644684925297477149298484753858863551357266259935298184325926848958828192317538375317946457985874965434486829387647425222952585293626473351211161684297351932771462665621764392833122236577353669215833721772482863775629244619639234636853267934895783891823877845198326665728659328729472456175285229681244974389248235457688922179237895954959228638193933854787917647154837695422429184757725387589969781672596568421191236374563718951738499591454571728641951699981615249635314789251239677393251756396 \ No newline at end of file +6592822488931338589815525425236818285229555616392928433262436847386544514648645288129834834862363847542262953164877694234514375164927616649264122487182321437459646851966649732474925353281699895326824852555747127547527163197544539468632369858413232684269835288817735678173986264554586412678364433327621627496939956645283712453265255261565511586373551439198276373843771249563722914847255524452675842558622845416218195374459386785618255129831539984559644185369543662821311686162137672168266152494656448824719791398797359326412235723234585539515385352426579831251943911197862994974133738196775618715739412713224837531544346114877971977411275354168752719858889347588136787894798476123335894514342411742111135337286449968879251481449757294167363867119927811513529711239534914119292833111624483472466781475951494348516125474142532923858941279569675445694654355314925386833175795464912974865287564866767924677333599828829875283753669783176288899797691713766199641716546284841387455733132519649365113182432238477673375234793394595435816924453585513973119548841577126141962776649294322189695375451743747581241922657947182232454611837512564776273929815169367899818698892234618847815155578736875295629917247977658723868641411493551796998791839776335793682643551875947346347344695869874564432566956882395424267187552799458352121248147371938943799995158617871393289534789214852747976587432857675156884837634687257363975437535621197887877326295229195663235129213398178282549432599455965759999159247295857366485345759516622427833518837458236123723353817444545271644684925297477149298484753858863551357266259935298184325926848958828192317538375317946457985874965434486829387647425222952585293626473351211161684297351932771462665621764392833122236577353669215833721772482863775629244619639234636853267934895783891823877845198326665728659328729472456175285229681244974389248235457688922179237895954959228638193933854787917647154837695422429184757725387589969781672596568421191236374563718951738499591454571728641951699981615249635314789251239677393251756396 diff --git a/internal/puzzles/input/data/2018/day01.txt b/internal/puzzles/input/data/2018/day01.txt index 242093a7..e73449ec 100644 --- a/internal/puzzles/input/data/2018/day01.txt +++ b/internal/puzzles/input/data/2018/day01.txt @@ -986,4 +986,4 @@ -7 -6 -3 --124478 \ No newline at end of file +-124478 diff --git a/internal/puzzles/input/data/2019/day01.txt b/internal/puzzles/input/data/2019/day01.txt index 77a1306f..046a3b55 100644 --- a/internal/puzzles/input/data/2019/day01.txt +++ b/internal/puzzles/input/data/2019/day01.txt @@ -97,4 +97,4 @@ 119815 125634 104335 -138295 \ No newline at end of file +138295 diff --git a/internal/puzzles/input/data/2019/day02.txt b/internal/puzzles/input/data/2019/day02.txt index 5110fba2..c54d3aad 100644 --- a/internal/puzzles/input/data/2019/day02.txt +++ b/internal/puzzles/input/data/2019/day02.txt @@ -1 +1 @@ -1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,10,1,19,2,9,19,23,2,13,23,27,1,6,27,31,2,6,31,35,2,13,35,39,1,39,10,43,2,43,13,47,1,9,47,51,1,51,13,55,1,55,13,59,2,59,13,63,1,63,6,67,2,6,67,71,1,5,71,75,2,6,75,79,1,5,79,83,2,83,6,87,1,5,87,91,1,6,91,95,2,95,6,99,1,5,99,103,1,6,103,107,1,107,2,111,1,111,5,0,99,2,14,0,0 \ No newline at end of file +1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,10,1,19,2,9,19,23,2,13,23,27,1,6,27,31,2,6,31,35,2,13,35,39,1,39,10,43,2,43,13,47,1,9,47,51,1,51,13,55,1,55,13,59,2,59,13,63,1,63,6,67,2,6,67,71,1,5,71,75,2,6,75,79,1,5,79,83,2,83,6,87,1,5,87,91,1,6,91,95,2,95,6,99,1,5,99,103,1,6,103,107,1,107,2,111,1,111,5,0,99,2,14,0,0 diff --git a/internal/puzzles/input/data/2019/day03.txt b/internal/puzzles/input/data/2019/day03.txt index d8a6b321..80247b79 100644 --- a/internal/puzzles/input/data/2019/day03.txt +++ b/internal/puzzles/input/data/2019/day03.txt @@ -1,2 +1,2 @@ R1008,U336,R184,D967,R451,D742,L235,U219,R57,D439,R869,U207,L574,U670,L808,D675,L203,D370,L279,U448,L890,U297,R279,D613,L411,D530,L372,D88,R986,U444,R319,D95,L385,D674,R887,U855,R794,U783,R633,U167,L587,D545,L726,D196,R681,U609,R677,U881,R153,D724,L63,U246,R343,U315,R580,U872,L516,U95,R463,D809,R9,U739,R540,U670,L434,D699,L158,U47,L383,D483,L341,U61,R933,D269,R816,D589,R488,D169,R972,D534,L995,D277,L887,D657,R628,D322,R753,U813,L284,D237,R676,D880,L50,D965,L401,D619,R858,U313,L156,U535,R664,U447,L251,U168,L352,U881,L734,U270,L177,D903,L114,U998,L102,D149,R848,D586,L98,D157,R942,U496,R857,U362,R398,U86,R469,U358,L721,D631,R176,D365,L112,U472,L557,D153,R97,D639,L457,U566,R570,U106,R504,D292,L94,U499,R358,U653,L704,D627,R544,D24,L407,U513,R28,U643,L510,U579,R825,D376,L867,U999,R134,D734,R654,D989,L920,U872,R64,U626,R751,D425,R620,U274,L471,D83,R979,U577,L43,D320,R673,D187,R300,U134,L451,D717,R857,U576,R570,U988,R745,U840,R799,U809,R573,U354,L208,D976,L417,U473,L555,U563,L955,U823,R712,D869,L145,D735,L780,D74,R421,U42,L158,U689,R718,D455,L670,U128,L744,U401,R149,U102,L122,U832,R872,D40,R45,D325,L553,U980,L565,D497,L435,U647,L209,D822,L593,D28,R936,U95,R349,U511,L243,U895,R421,U336,L986,U264,R376,D183,R480,D947,R416,D706,R118,D799,R424,D615,R384,U185,L338,U14,R576,D901,L734,D417,L62,D254,R784,D973,R987,D848,R32,D72,L535,D633,L668,D664,R308,D474,L418,D39,L473,U388,L518,D544,R118,D948,L844,D956,R605,U14,L948,D78,L689,U443,L996,U932,R81,D879,R556,D633,R131 -L993,U227,L414,U228,L304,U53,R695,U765,R162,D264,L530,U870,R771,D395,R27,D200,L235,D834,L559,D128,R284,U912,L959,U358,R433,U404,L539,U799,R271,D734,L104,U261,R812,D15,L474,U887,R606,U366,L694,U156,R385,D667,L329,D434,R745,U776,L319,U756,R208,D457,R705,U999,R284,U98,L657,U214,R639,D937,R675,U444,L891,D587,L914,D4,R294,D896,R534,D584,L887,U878,L807,U202,R505,U234,L284,D5,R667,U261,L127,D482,R777,D223,L707,D468,L606,U345,L509,D967,R437,U995,R28,D376,L2,D959,L814,U702,L38,D154,L79,D439,L259,U143,L376,U700,R894,U165,L300,D58,R631,D47,R684,U935,R262,D857,R797,D599,L705,U792,R439,D444,R398,D887,L81,D40,R671,U332,L820,U252,R691,U412,L794,D661,R810,U157,R858,U566,R892,D543,R10,D725,L653,D812,R733,D804,R816,U862,R994,U221,L33,U271,R766,D591,R575,D970,R152,D693,L916,U404,L658,D847,L605,D433,L583,U587,L129,D103,R407,U780,L901,D676,L846,D687,L9,D47,R295,D597,L808,U134,L186,D676,L62,U305,L73,D369,L468,U30,L472,U280,L413,U961,L98,D966,R308,D178,L21,D789,L871,D671,R665,U927,L906,U633,L135,D894,R110,D205,R324,D665,R143,D450,L978,U385,R442,D853,L518,U542,R211,U857,R119,D872,L246,U380,L874,U463,R153,U982,R832,D784,L652,U545,R71,U386,R427,D827,R986,U870,R959,U232,R509,U675,R196,U389,R944,U149,R152,U571,R527,U495,L441,U511,L899,D996,L707,D455,L358,D423,L14,D427,R144,D703,L243,U157,R876,D538,R26,D577,L385,U622,L149,D852,L225,U475,L811,D520,L226,U523,L338,D79,R565,U766,L609,U496,L189,D446,R63,U396,L629,U312,L841,D639,R466,U808,L60,D589,L146,U114,R165,U96,L809,D704,L61 \ No newline at end of file +L993,U227,L414,U228,L304,U53,R695,U765,R162,D264,L530,U870,R771,D395,R27,D200,L235,D834,L559,D128,R284,U912,L959,U358,R433,U404,L539,U799,R271,D734,L104,U261,R812,D15,L474,U887,R606,U366,L694,U156,R385,D667,L329,D434,R745,U776,L319,U756,R208,D457,R705,U999,R284,U98,L657,U214,R639,D937,R675,U444,L891,D587,L914,D4,R294,D896,R534,D584,L887,U878,L807,U202,R505,U234,L284,D5,R667,U261,L127,D482,R777,D223,L707,D468,L606,U345,L509,D967,R437,U995,R28,D376,L2,D959,L814,U702,L38,D154,L79,D439,L259,U143,L376,U700,R894,U165,L300,D58,R631,D47,R684,U935,R262,D857,R797,D599,L705,U792,R439,D444,R398,D887,L81,D40,R671,U332,L820,U252,R691,U412,L794,D661,R810,U157,R858,U566,R892,D543,R10,D725,L653,D812,R733,D804,R816,U862,R994,U221,L33,U271,R766,D591,R575,D970,R152,D693,L916,U404,L658,D847,L605,D433,L583,U587,L129,D103,R407,U780,L901,D676,L846,D687,L9,D47,R295,D597,L808,U134,L186,D676,L62,U305,L73,D369,L468,U30,L472,U280,L413,U961,L98,D966,R308,D178,L21,D789,L871,D671,R665,U927,L906,U633,L135,D894,R110,D205,R324,D665,R143,D450,L978,U385,R442,D853,L518,U542,R211,U857,R119,D872,L246,U380,L874,U463,R153,U982,R832,D784,L652,U545,R71,U386,R427,D827,R986,U870,R959,U232,R509,U675,R196,U389,R944,U149,R152,U571,R527,U495,L441,U511,L899,D996,L707,D455,L358,D423,L14,D427,R144,D703,L243,U157,R876,D538,R26,D577,L385,U622,L149,D852,L225,U475,L811,D520,L226,U523,L338,D79,R565,U766,L609,U496,L189,D446,R63,U396,L629,U312,L841,D639,R466,U808,L60,D589,L146,U114,R165,U96,L809,D704,L61 diff --git a/internal/puzzles/input/data/2019/day04.txt b/internal/puzzles/input/data/2019/day04.txt index c3deaa89..9d768076 100644 --- a/internal/puzzles/input/data/2019/day04.txt +++ b/internal/puzzles/input/data/2019/day04.txt @@ -1 +1 @@ -108457-562041 \ No newline at end of file +108457-562041 diff --git a/internal/puzzles/input/data/2020/day01.txt b/internal/puzzles/input/data/2020/day01.txt index 804bf4ef..c3dadb42 100644 --- a/internal/puzzles/input/data/2020/day01.txt +++ b/internal/puzzles/input/data/2020/day01.txt @@ -197,4 +197,4 @@ 1593 656 1530 -1743 \ No newline at end of file +1743 diff --git a/internal/puzzles/input/data/2020/day02.txt b/internal/puzzles/input/data/2020/day02.txt index 6fa5790e..fb33d054 100644 --- a/internal/puzzles/input/data/2020/day02.txt +++ b/internal/puzzles/input/data/2020/day02.txt @@ -997,4 +997,4 @@ 11-13 m: mmmmvmbmmmmmmmmmmmqm 10-11 g: blpbhgjcgmgg 9-11 d: dddpddsnzdkqpdddk -4-9 f: xfcfthqzw \ No newline at end of file +4-9 f: xfcfthqzw diff --git a/internal/puzzles/solutions/2015/day01/solution.go b/internal/puzzles/solutions/2015/day01/solution.go index 11fecc06..133b6877 100644 --- a/internal/puzzles/solutions/2015/day01/solution.go +++ b/internal/puzzles/solutions/2015/day01/solution.go @@ -1,3 +1,4 @@ +// Package day01 contains solution for https://adventofcode.com/2015/day/1 puzzle. package day01 import ( @@ -10,29 +11,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2015" -) - -type solution struct { - year string - name string -} +type solution struct{} -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } func (s solution) Year() string { - return s.year + return puzzles.Year2015.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(in io.Reader) (string, error) { diff --git a/internal/puzzles/solutions/2015/day01/solution_test.go b/internal/puzzles/solutions/2015/day01/solution_test.go index 06b629a1..85a17df4 100644 --- a/internal/puzzles/solutions/2015/day01/solution_test.go +++ b/internal/puzzles/solutions/2015/day01/solution_test.go @@ -9,9 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -19,16 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("(())"), }, @@ -37,9 +31,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("()()"), }, @@ -48,9 +39,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("((("), }, @@ -59,9 +47,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("(()(()("), }, @@ -70,9 +55,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("))((((("), }, @@ -81,9 +63,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("())"), }, @@ -92,9 +71,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("))("), }, @@ -103,9 +79,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader(")))"), }, @@ -114,9 +87,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader(")())())"), }, @@ -129,10 +99,6 @@ func Test_solution_Part1(t *testing.T) { 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) @@ -147,9 +113,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -157,16 +121,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader(")"), }, @@ -175,9 +135,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("()())"), }, @@ -190,10 +147,6 @@ func Test_solution_Part2(t *testing.T) { 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) diff --git a/internal/puzzles/solutions/2016/day01/solution.go b/internal/puzzles/solutions/2016/day01/solution.go index cfc42722..dff31653 100644 --- a/internal/puzzles/solutions/2016/day01/solution.go +++ b/internal/puzzles/solutions/2016/day01/solution.go @@ -1,3 +1,4 @@ +// Package day01 contains solution for https://adventofcode.com/2016/day/1 puzzle. package day01 import ( @@ -13,29 +14,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2016" -) - -type solution struct { - year string - name string -} +type solution struct{} -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } func (s solution) Year() string { - return s.year + return puzzles.Year2016.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(input io.Reader) (string, error) { @@ -129,19 +119,19 @@ type position struct { } func (p *position) addX(n int) { - p.x = p.x + n + p.x += n } func (p *position) addY(n int) { - p.y = p.y + n + p.y += n } func (p *position) subX(n int) { - p.x = p.x - n + p.x -= n } func (p *position) subY(n int) { - p.y = p.y - n + p.y -= n } func (p position) manhattan() int { @@ -228,11 +218,70 @@ func (c *cab) Move(t turn, steps int) error { c.n.moveSouth(steps) case westDirection: c.n.moveWest(steps) + case unknownDirection, sentinelDirection: + return errInvalidDirect } return nil } +type navigator struct { + record chan position + pos position + track track + mu *sync.Mutex + wg *sync.WaitGroup + revisited []position +} + +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{}, + } +} + +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 (n *navigator) moveNorth(steps int) { for i := 0; i < steps; i++ { n.mu.Lock() @@ -273,10 +322,6 @@ func (n *navigator) moveWest(steps int) { } } -func (c *cab) Track() track { - return c.n.track -} - func (n navigator) Pos() position { n.mu.Lock() defer n.mu.Unlock() @@ -285,10 +330,10 @@ func (n navigator) Pos() position { } // Example: L4, R5 -var re = regexp.MustCompile(`(?msi)(L|R)(\d+)`) +var re = regexp.MustCompile(`(?msi)([LR])(\d+)`) const ( - fullMatchPos = iota + _ = iota turnPos stepsPos @@ -307,6 +352,7 @@ func splitCommand(cmd string) (turn, int, error) { if err != nil { return "", 0, fmt.Errorf("turnFromstring: %w", err) } + s, err := strconv.Atoi(parts[stepsPos]) if err != nil { return "", 0, fmt.Errorf("invalid steps num: %w", err) @@ -315,63 +361,6 @@ func splitCommand(cmd string) (turn, int, error) { 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 { diff --git a/internal/puzzles/solutions/2016/day01/solution_test.go b/internal/puzzles/solutions/2016/day01/solution_test.go index 66d48584..72a44b35 100644 --- a/internal/puzzles/solutions/2016/day01/solution_test.go +++ b/internal/puzzles/solutions/2016/day01/solution_test.go @@ -9,9 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -19,16 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("R2, L3"), }, @@ -37,9 +31,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("R2, R2, R2"), }, @@ -48,9 +39,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("R5, L5, R5, R3"), }, @@ -63,11 +51,6 @@ func Test_solution_Part1(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - name: tt.fields.name, - year: year, - } - got, err := s.Part1(tt.args.input) if tt.wantErr { assert.Error(t, err) @@ -82,9 +65,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -92,16 +73,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("R8, R4, R4, R8"), }, @@ -114,10 +91,6 @@ func Test_solution_Part2(t *testing.T) { 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) diff --git a/internal/puzzles/solutions/2017/day01/solution.go b/internal/puzzles/solutions/2017/day01/solution.go index 1abadf80..22761772 100644 --- a/internal/puzzles/solutions/2017/day01/solution.go +++ b/internal/puzzles/solutions/2017/day01/solution.go @@ -1,3 +1,4 @@ +// Package day01 contains solution for https://adventofcode.com/2017/day/1 puzzle. package day01 import ( @@ -10,29 +11,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2017" -) - -type solution struct { - year string - name string -} +type solution struct{} -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } func (s solution) Year() string { - return s.year + return puzzles.Year2017.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(in io.Reader) (string, error) { @@ -74,6 +64,8 @@ func part2(in io.Reader) (string, error) { } func makeList(in io.Reader) ([]int, error) { + const newline = '\n' + var list []int reader := bufio.NewReader(in) @@ -82,13 +74,16 @@ func makeList(in io.Reader) ([]int, error) { r, _, err := reader.ReadRune() if err != nil { if errors.Is(err, io.EOF) { - break } return nil, fmt.Errorf("read rune: %w", err) } + if r == newline { + continue + } + n, err := strconv.Atoi(string(r)) if err != nil { return nil, fmt.Errorf("strconv atoi: %w", err) @@ -120,11 +115,12 @@ func (i iterator) Sum() int { sum int ) - rightBound := len(i.list) - 1 + listlen := len(i.list) + rightBound := listlen - 1 lastidx := rightBound if !i.isCircular { - lastidx = lastidx - i.shift + lastidx -= i.shift } for cursorStart <= lastidx { @@ -133,7 +129,7 @@ func (i iterator) Sum() int { cursorEnd += i.shift if i.isCircular { if cursorEnd > rightBound { - cursorEnd = cursorEnd - len(i.list) + cursorEnd -= listlen } } diff --git a/internal/puzzles/solutions/2017/day01/solution_test.go b/internal/puzzles/solutions/2017/day01/solution_test.go index a5bb3965..4fde47b2 100644 --- a/internal/puzzles/solutions/2017/day01/solution_test.go +++ b/internal/puzzles/solutions/2017/day01/solution_test.go @@ -9,10 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - year string - } + var s solution type args struct { input io.Reader @@ -20,17 +17,13 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { - name: `1122 produces a sum of 3 (1 + 2) because the first digit (1) matches the second digit and the third digit (2) matches the fourth digit`, - fields: fields{ - name: "", - year: "", - }, + name: "1122 produces a sum of 3 (1 + 2) because the first digit (1) matches the second digit and the " + + "third digit (2) matches the fourth digit", args: args{ input: strings.NewReader("1122"), }, @@ -39,10 +32,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: `1111 produces 4 because each digit (all 1) matches the next.`, - fields: fields{ - name: "", - year: "", - }, args: args{ input: strings.NewReader("1111"), }, @@ -51,10 +40,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: `1234 produces 0 because no digit matches the next`, - fields: fields{ - name: "", - year: "", - }, args: args{ input: strings.NewReader("1234"), }, @@ -63,10 +48,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: `91212129 produces 9 because the only digit that matches the next one is the last digit, 9`, - fields: fields{ - name: "", - year: "", - }, args: args{ input: strings.NewReader("91212129"), }, @@ -79,10 +60,6 @@ func Test_solution_Part1(t *testing.T) { 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) @@ -97,9 +74,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -107,16 +82,13 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { - name: "`1212` produces `6`: the list contains `4` items, and all four digits match the digit `2` items ahead", - fields: fields{ - name: "", - }, + name: "`1212` produces `6`: the list contains `4` items, and all four " + + "digits match the digit `2` items ahead", args: args{ input: strings.NewReader("1212"), }, @@ -125,9 +97,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`1221` produces `0`, because every comparison is between a `1` and a `2`", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("1221"), }, @@ -136,9 +105,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`123425` produces `4`, because both `2`s match each other, but no other digit has a match", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("123425"), }, @@ -147,9 +113,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`123123` produces `12`", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("123123"), }, @@ -158,25 +121,26 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`12131415` produces `4`", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("12131415"), }, want: "4", wantErr: false, }, + { + name: "`12131415\n\n` produces `4`", + args: args{ + input: strings.NewReader("12131415\n\n"), + }, + want: "4", + 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.Part2(tt.args.input) if tt.wantErr { assert.Error(t, err) diff --git a/internal/puzzles/solutions/2018/day01/solution.go b/internal/puzzles/solutions/2018/day01/solution.go index 1a4160f1..412078c4 100644 --- a/internal/puzzles/solutions/2018/day01/solution.go +++ b/internal/puzzles/solutions/2018/day01/solution.go @@ -1,3 +1,4 @@ +// Package day01 contains solution for https://adventofcode.com/2018/day/1 puzzle. package day01 import ( @@ -12,29 +13,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2018" -) - -type solution struct { - year string - name string -} +type solution struct{} -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } func (s solution) Year() string { - return s.year + return puzzles.Year2018.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(in io.Reader) (string, error) { @@ -155,7 +145,7 @@ func (d *device) Apply(delta freqDelta) { d.mu.Lock() defer d.mu.Unlock() - d.seen[d.frequency] = d.seen[d.frequency] + 1 + d.seen[d.frequency]++ } func (d *device) CurFreq() int { diff --git a/internal/puzzles/solutions/2018/day01/solution_test.go b/internal/puzzles/solutions/2018/day01/solution_test.go index e600232c..02ae1738 100644 --- a/internal/puzzles/solutions/2018/day01/solution_test.go +++ b/internal/puzzles/solutions/2018/day01/solution_test.go @@ -9,10 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - year string - } + var s solution type args struct { input io.Reader @@ -20,17 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "+1, +1, +1` results in `3`", - fields: fields{ - name: "day01", - year: "2018", - }, args: args{ input: strings.NewReader("+1\n+1\n+1"), }, @@ -39,10 +31,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "`+1, +1, -2` results in `0`", - fields: fields{ - name: "day01", - year: "2018", - }, args: args{ input: strings.NewReader("+1\n+1\n-2"), }, @@ -51,10 +39,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "`-1, -2, -3` results in `-6`", - fields: fields{ - name: "day01", - year: "2018", - }, args: args{ input: strings.NewReader("-1\n-2\n-3"), }, @@ -63,9 +47,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "`+1, -2, +3, +1` results in `3`", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("+1\n-2\n+3\n+1"), }, @@ -78,11 +59,6 @@ func Test_solution_Part1(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } - got, err := s.Part1(tt.args.input) if tt.wantErr { assert.Error(t, err) @@ -97,10 +73,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - year string - } + var s solution type args struct { input io.Reader @@ -108,17 +81,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "`+1, -2, +3, +1` results in `2`", - fields: fields{ - name: "2018", - year: "day01", - }, args: args{ input: strings.NewReader("+1\n-2\n+3\n+1"), }, @@ -127,10 +95,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`+1, -1` first reaches `0` twice.", - fields: fields{ - name: "2018", - year: "day01", - }, args: args{ input: strings.NewReader("+1\n-1"), }, @@ -139,10 +103,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`+3, +3, +4, -2, -4` first reaches `10` twice.", - fields: fields{ - name: "2018", - year: "day01", - }, args: args{ input: strings.NewReader("+3\n+3\n+4\n-2\n-4"), }, @@ -151,10 +111,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`-6, +3, +8, +5, -6` first reaches `5` twice.", - fields: fields{ - name: "2018", - year: "day01", - }, args: args{ input: strings.NewReader("-6\n+3\n+8\n+5\n-6"), }, @@ -163,10 +119,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "`+7, +7, -2, -7, -4` first reaches `14` twice.", - fields: fields{ - name: "2018", - year: "day01", - }, args: args{ input: strings.NewReader("+7\n+7\n-2\n-7\n-4"), }, @@ -179,11 +131,6 @@ func Test_solution_Part2(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } - got, err := s.Part2(tt.args.input) if tt.wantErr { assert.Error(t, err) @@ -201,6 +148,7 @@ func Test_getFreqDelta(t *testing.T) { type args struct { line string } + tests := []struct { name string args args @@ -230,6 +178,7 @@ func Test_getFreqDelta(t *testing.T) { wantErr: true, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getFreqDelta(tt.args.line) diff --git a/internal/puzzles/solutions/2019/day01/solution.go b/internal/puzzles/solutions/2019/day01/solution.go index 9a38b469..285dbbb5 100644 --- a/internal/puzzles/solutions/2019/day01/solution.go +++ b/internal/puzzles/solutions/2019/day01/solution.go @@ -1,4 +1,4 @@ -// Package day01 solves https://adventofcode.com/2019/day/1 +// Package day01 contains solution for https://adventofcode.com/2019/day/1 puzzle. package day01 import ( @@ -10,25 +10,14 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2019" -) - -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2019.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(input io.Reader) (string, error) { @@ -39,8 +28,8 @@ func (s solution) Part2(input io.Reader) (string, error) { return calc(input, calcPart2) } -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } const ( @@ -57,7 +46,7 @@ func (m module) fuel() int { diff := mass % divFactor if diff != 0 { - mass = mass - diff + mass -= diff } f := (mass / divFactor) - subFactor diff --git a/internal/puzzles/solutions/2019/day02/solution.go b/internal/puzzles/solutions/2019/day02/solution.go index e51486c4..85979633 100644 --- a/internal/puzzles/solutions/2019/day02/solution.go +++ b/internal/puzzles/solutions/2019/day02/solution.go @@ -1,4 +1,4 @@ -// Package day02 solves https://adventofcode.com/2019/day/2 +// Package day02 contains solution for https://adventofcode.com/2019/day/2 puzzle. package day02 import ( @@ -11,25 +11,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles/utils/intcomputer" ) -const ( - puzzleName = "day02" - year = "2019" -) - func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2019.String() +} + +func (s solution) Day() string { + return puzzles.Day02.String() } func (s solution) Part1(input io.Reader) (string, error) { @@ -38,7 +31,12 @@ func (s solution) Part1(input io.Reader) (string, error) { return "", fmt.Errorf("failed to init computer: %w", err) } - c.Input(12, 2) + const ( + firstPos = 12 + secondPos = 2 + ) + + c.Input(firstPos, secondPos) res, err := c.Execute() if err != nil { @@ -76,10 +74,6 @@ func (s solution) Part2(input io.Reader) (string, error) { return "", errors.New("can't found non and verb") } -func nounVerb(noun int, verb int) int { +func nounVerb(noun, verb int) int { return 100*noun + verb } - -func (s solution) Name() string { - return s.name -} diff --git a/internal/puzzles/solutions/2019/day02/solution_test.go b/internal/puzzles/solutions/2019/day02/solution_test.go index a4cf78a3..15017c10 100644 --- a/internal/puzzles/solutions/2019/day02/solution_test.go +++ b/internal/puzzles/solutions/2019/day02/solution_test.go @@ -40,9 +40,7 @@ func Test_nounVerb(t *testing.T) { } func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -50,16 +48,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("1,9,10,3,2,3,11,0,99,30,40,50,2,3"), }, @@ -72,10 +66,6 @@ func Test_solution_Part1(t *testing.T) { 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) diff --git a/internal/puzzles/solutions/2019/day03/solution.go b/internal/puzzles/solutions/2019/day03/solution.go index 2b2021b3..3083ef54 100644 --- a/internal/puzzles/solutions/2019/day03/solution.go +++ b/internal/puzzles/solutions/2019/day03/solution.go @@ -1,4 +1,4 @@ -// Package day03 solves https://adventofcode.com/2019/day/3 +// Package day03 contains solution for https://adventofcode.com/2019/day/3 puzzle. package day03 import ( @@ -13,25 +13,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day03" - year = "2019" -) - func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2019.String() +} + +func (s solution) Day() string { + return puzzles.Day03.String() } func (s solution) Part1(input io.Reader) (string, error) { @@ -74,10 +67,6 @@ func (s solution) Part2(input io.Reader) (string, error) { return strconv.Itoa(stps[0]), nil } -func (s solution) Name() string { - return s.name -} - type wire struct { pos pos step stepper @@ -192,7 +181,7 @@ func (p pos) manhattan() int { return x + y } -func findCross(wm1 map[pos]int, wm2 map[pos]int) []pos { +func findCross(wm1, wm2 map[pos]int) []pos { res := make([]pos, 0, len(wm1)) for p := range wm1 { @@ -207,8 +196,13 @@ func findCross(wm1 map[pos]int, wm2 map[pos]int) []pos { } func runWires(input io.Reader) ([]map[pos]int, error) { + const ( + wiresnum = 2 + ) + + res := make([]map[pos]int, 0, wiresnum) + scanner := bufio.NewScanner(input) - res := make([]map[pos]int, 0, 2) for scanner.Scan() { line := scanner.Text() diff --git a/internal/puzzles/solutions/2019/day03/solution_test.go b/internal/puzzles/solutions/2019/day03/solution_test.go index a5d894cc..8bb4b704 100644 --- a/internal/puzzles/solutions/2019/day03/solution_test.go +++ b/internal/puzzles/solutions/2019/day03/solution_test.go @@ -9,9 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -19,16 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "distance 6", - fields: fields{ - name: "part1", - }, args: args{ input: strings.NewReader("U7,R6,D4,L4\n" + "R8,U5,L5,D3"), @@ -38,9 +32,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "distance 159", - fields: fields{ - name: "part1", - }, args: args{ input: strings.NewReader("R75,D30,R83,U83,L12,D49,R71,U7,L72\n" + "U62,R66,U55,R34,D71,R55,D58,R83"), @@ -50,9 +41,6 @@ func Test_solution_Part1(t *testing.T) { }, { name: "distance 159", - fields: fields{ - name: "part1", - }, args: args{ input: strings.NewReader("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51\n" + "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7"), @@ -66,10 +54,6 @@ func Test_solution_Part1(t *testing.T) { 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) @@ -129,9 +113,7 @@ func Test_findCross(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -139,16 +121,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "610", - fields: fields{ - name: "test", - }, args: args{ input: strings.NewReader("R75,D30,R83,U83,L12,D49,R71,U7,L72\n" + "U62,R66,U55,R34,D71,R55,D58,R83"), @@ -158,9 +136,6 @@ func Test_solution_Part2(t *testing.T) { }, { name: "410", - fields: fields{ - name: "test", - }, args: args{ input: strings.NewReader("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51\n" + "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7"), @@ -174,10 +149,6 @@ func Test_solution_Part2(t *testing.T) { 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) diff --git a/internal/puzzles/solutions/2019/day04/solution.go b/internal/puzzles/solutions/2019/day04/solution.go index b85b06db..ded564a6 100644 --- a/internal/puzzles/solutions/2019/day04/solution.go +++ b/internal/puzzles/solutions/2019/day04/solution.go @@ -1,4 +1,4 @@ -// Package day04 solves https://adventofcode.com/2019/day/4 +// Package day04 contains solution for https://adventofcode.com/2019/day/4 puzzle. package day04 import ( @@ -12,25 +12,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day04" - year = "2019" -) - func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2019.String() +} + +func (s solution) Day() string { + return puzzles.Day04.String() } func (s solution) Part1(input io.Reader) (string, error) { @@ -41,19 +34,17 @@ func (s solution) Part2(input io.Reader) (string, error) { return run(input, isPasswordPart2) } -func (s solution) Name() string { - return s.name -} - func run(input io.Reader, criteria isPwdFunc) (string, error) { - buf := new(bytes.Buffer) + var buf bytes.Buffer if _, err := buf.ReadFrom(input); err != nil { return "", fmt.Errorf("failed to read: %w", err) } const limitsNum = 2 - limits := strings.Split(buf.String(), "-") // should be 2: low and high + raw := strings.TrimSpace(buf.String()) + + limits := strings.Split(raw, "-") // should be 2: low and high if len(limits) != limitsNum { return "", errors.New("invalid number of limits") } diff --git a/internal/puzzles/solutions/2019/day04/solution_test.go b/internal/puzzles/solutions/2019/day04/solution_test.go index d2fad9c0..ba89adef 100644 --- a/internal/puzzles/solutions/2019/day04/solution_test.go +++ b/internal/puzzles/solutions/2019/day04/solution_test.go @@ -9,9 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -19,16 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "", - fields: fields{ - name: "", - }, args: args{ input: strings.NewReader("111000-111222"), }, @@ -41,10 +35,6 @@ func Test_solution_Part1(t *testing.T) { 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) @@ -294,9 +284,7 @@ func Test_isPasswordPart2(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - name string - } + var s solution type args struct { input io.Reader @@ -304,32 +292,32 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { - name: "", - fields: fields{ - name: "", - }, + name: "`111000-111222` produces `8`", args: args{ input: strings.NewReader("111000-111222"), }, want: "8", wantErr: false, }, + { + name: "`111000-111222\n\n` produces `8`", + args: args{ + input: strings.NewReader("111000-111222\n\n"), + }, + want: "8", + 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.Part2(tt.args.input) if tt.wantErr { assert.Error(t, err) diff --git a/internal/puzzles/solutions/2020/day01/solution.go b/internal/puzzles/solutions/2020/day01/solution.go index c67709cd..60da3dcc 100644 --- a/internal/puzzles/solutions/2020/day01/solution.go +++ b/internal/puzzles/solutions/2020/day01/solution.go @@ -1,3 +1,4 @@ +// Package day01 contains solution for https://adventofcode.com/2020/day/1 puzzle. package day01 import ( @@ -10,29 +11,18 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day01" - year = "2020" -) - -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2020.String() } -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day01.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } func (s solution) Part1(input io.Reader) (string, error) { @@ -43,14 +33,14 @@ func (s solution) Part1(input io.Reader) (string, error) { for scanner.Scan() { entry, err := strconv.Atoi(scanner.Text()) if err != nil { - return "", fmt.Errorf("[%s:%s]: part1: faied to parse int: %w", s.year, s.name, err) + return "", fmt.Errorf("faied to parse int: %w", err) } expensereport[entry] = true } if err := scanner.Err(); err != nil { - return "", fmt.Errorf("[%s:%s]: part1: scanner error: %w", s.year, s.name, err) + return "", fmt.Errorf("scanner error: %w", err) } const ( @@ -64,6 +54,7 @@ func (s solution) Part1(input io.Reader) (string, error) { for e := range expensereport { a = e b = dest - e + if expensereport[b] { break } @@ -82,14 +73,14 @@ func (s solution) Part2(input io.Reader) (string, error) { for scanner.Scan() { entry, err := strconv.Atoi(scanner.Text()) if err != nil { - return "", fmt.Errorf("[%s:%s]: part2: faied to parse int: %w", s.year, s.name, err) + return "", fmt.Errorf("faied to parse int: %w", err) } expensereport = append(expensereport, entry) } if err := scanner.Err(); err != nil { - return "", fmt.Errorf("[%s:%s]: part2: scanner error: %w", s.year, s.name, err) + return "", fmt.Errorf("scanner error: %w", err) } sort.Ints(expensereport) @@ -119,11 +110,10 @@ loop: } } } - } if !found { - return "", fmt.Errorf("[%s:%s]: part2: answer not found", s.year, s.name) + return "", fmt.Errorf("answer not found") } res := a * b * c diff --git a/internal/puzzles/solutions/2020/day01/solution_test.go b/internal/puzzles/solutions/2020/day01/solution_test.go index 2690fcec..910097d0 100644 --- a/internal/puzzles/solutions/2020/day01/solution_test.go +++ b/internal/puzzles/solutions/2020/day01/solution_test.go @@ -9,10 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - year string - name string - } + var s solution type args struct { input io.Reader @@ -20,17 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "test example from description", - fields: fields{ - year: year, - name: puzzleName, - }, args: args{ input: strings.NewReader("1721\n979\n366\n299\n675\n1456"), }, @@ -43,10 +35,6 @@ func Test_solution_Part1(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } got, err := s.Part1(tt.args.input) if tt.wantErr { assert.Error(t, err) @@ -61,10 +49,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - year string - name string - } + var s solution type args struct { input io.Reader @@ -72,17 +57,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "test example from description", - fields: fields{ - year: year, - name: puzzleName, - }, args: args{ input: strings.NewReader("1721\n979\n366\n299\n675\n1456"), }, @@ -95,11 +75,6 @@ func Test_solution_Part2(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } - got, err := s.Part2(tt.args.input) if tt.wantErr { assert.Error(t, err) diff --git a/internal/puzzles/solutions/2020/day02/solution.go b/internal/puzzles/solutions/2020/day02/solution.go index ad7e4156..e25f47bb 100644 --- a/internal/puzzles/solutions/2020/day02/solution.go +++ b/internal/puzzles/solutions/2020/day02/solution.go @@ -1,3 +1,4 @@ +// Package day02 contains solution for https://adventofcode.com/2020/day/2 puzzle. package day02 import ( @@ -11,210 +12,128 @@ import ( "github.com/obalunenko/advent-of-code/internal/puzzles" ) -const ( - puzzleName = "day02" - year = "2020" -) - -type solution struct { - year string - name string -} +type solution struct{} func (s solution) Year() string { - return s.year + return puzzles.Year2020.String() } -func (s solution) Name() string { - return s.name +func (s solution) Day() string { + return puzzles.Day02.String() } func init() { - puzzles.Register(solution{ - year: year, - name: puzzleName, - }) + puzzles.Register(solution{}) } -var ( - pwdRegex = regexp.MustCompile(`(?s)(\d{1,2})-(\d{1,2}) ([a-zA-Z]): ([[:word:]]+)`) -) - func (s solution) Part1(input io.Reader) (string, error) { - const ( - _ int = iota // full match - not needed - matchMin - matchMax - matchChar - matchPwd - - totalmatches = 5 - ) - - type passwordParams struct { - min int - max int - char string - } - - type inparams struct { - pwd string - pwdParams passwordParams - } - - var count int - - scanner := bufio.NewScanner(input) - - inchan := make(chan inparams) - reschan := make(chan bool) - donechan := make(chan struct{}) - - go func(in chan inparams, res chan bool, done chan struct{}) { + validationFunc := func(in chan inparams, res chan bool, done chan struct{}) { for d := range in { go func(in inparams, res chan bool, done chan struct{}) { count := strings.Count(in.pwd, in.pwdParams.char) - res <- count >= in.pwdParams.min && count <= in.pwdParams.max + res <- count >= in.pwdParams.firstPos && count <= in.pwdParams.secondPos done <- struct{}{} }(d, res, done) } - }(inchan, reschan, donechan) - - var operations int - - for scanner.Scan() { - line := scanner.Text() - submatch := pwdRegex.FindStringSubmatch(line) + } - if len(submatch) != totalmatches { - return "", fmt.Errorf("wrong matches[%d] for line[%s], should be [%d]", - len(submatch), line, totalmatches) - } + count, err := pwdCount(input, validationFunc) + if err != nil { + return "", fmt.Errorf("password validation: %w", err) + } - pwd := submatch[matchPwd] + return strconv.Itoa(count), nil +} - min, err := strconv.Atoi(submatch[matchMin]) - if err != nil { - return "", fmt.Errorf("failed to parse min[%s]: %w", submatch[matchMin], err) - } +func (s solution) Part2(input io.Reader) (string, error) { + validationFunc := func(in chan inparams, res chan bool, done chan struct{}) { + for d := range in { + go func(in inparams, res chan bool, done chan struct{}) { + var found int + if in.pwdParams.char == string([]rune(in.pwd)[in.pwdParams.firstPos-1]) { + found++ + } - max, err := strconv.Atoi(submatch[matchMax]) - if err != nil { - return "", fmt.Errorf("failed to parse max[%s]: %w", submatch[matchMax], err) - } + if in.pwdParams.char == string([]rune(in.pwd)[in.pwdParams.secondPos-1]) { + found++ + } - params := passwordParams{ - min: min, - max: max, - char: submatch[matchChar], - } + res <- found == 1 - in := inparams{ - pwd: pwd, - pwdParams: params, + done <- struct{}{} + }(d, res, done) } - - inchan <- in - - operations++ } - close(inchan) - -loop: - for { - select { - case isMatch := <-reschan: - if isMatch { - count++ - - } - case <-donechan: - operations-- - } - if operations == 0 { - break loop - } + count, err := pwdCount(input, validationFunc) + if err != nil { + return "", fmt.Errorf("password validation: %w", err) } - close(reschan) - close(donechan) - return strconv.Itoa(count), nil } -func (s solution) Part2(input io.Reader) (string, error) { - const ( - _ int = iota // full match - not needed - matchFirstPos - matchSecondPos - matchChar - matchPwd - - totalmatches = 5 - ) +var ( + pwdRegex = regexp.MustCompile(`(?s)(\d{1,2})-(\d{1,2}) ([a-zA-Z]): (\w+)`) +) - type passwordParams struct { - firstPos int - secondPos int - char string - } +const ( + _ int = iota // full match - not needed + matchFirst + matchSecond + matchChar + matchPwd - type inparams struct { - pwd string - pwdParams passwordParams - } + totalmatches = 5 +) + +type passwordParams struct { + firstPos int + secondPos int + char string +} + +type inparams struct { + pwd string + pwdParams passwordParams +} - var count int +type pwdValidationFunc func(in chan inparams, res chan bool, done chan struct{}) +func pwdCount(input io.Reader, validationFunc pwdValidationFunc) (int, error) { scanner := bufio.NewScanner(input) inchan := make(chan inparams) reschan := make(chan bool) donechan := make(chan struct{}) - go func(in chan inparams, res chan bool, done chan struct{}) { - for d := range in { - go func(in inparams, res chan bool, done chan struct{}) { - var found int - if in.pwdParams.char == string([]rune(in.pwd)[in.pwdParams.firstPos-1]) { - found++ - } - - if in.pwdParams.char == string([]rune(in.pwd)[in.pwdParams.secondPos-1]) { - found++ - } + go validationFunc(inchan, reschan, donechan) - res <- found > 0 && found < 2 - - done <- struct{}{} - }(d, res, done) - } - }(inchan, reschan, donechan) - - var operations int + var ( + count, operations int + ) for scanner.Scan() { line := scanner.Text() submatch := pwdRegex.FindStringSubmatch(line) if len(submatch) != totalmatches { - return "", fmt.Errorf("wrong matches[%d] for line[%s], should be [%d]", + return 0, fmt.Errorf("wrong matches[%d] for line[%s], should be [%d]", len(submatch), line, totalmatches) } pwd := submatch[matchPwd] - firstPos, err := strconv.Atoi(submatch[matchFirstPos]) + firstPos, err := strconv.Atoi(submatch[matchFirst]) if err != nil { - return "", fmt.Errorf("failed to parse first pos[%s]: %w", submatch[matchFirstPos], err) + return 0, fmt.Errorf("failed to parse first pos[%s]: %w", submatch[matchFirst], err) } - secondPos, err := strconv.Atoi(submatch[matchSecondPos]) + secondPos, err := strconv.Atoi(submatch[matchSecond]) if err != nil { - return "", fmt.Errorf("failed to parse second pos[%s]: %w", submatch[matchSecondPos], err) + return 0, fmt.Errorf("failed to parse second pos[%s]: %w", submatch[matchSecond], err) } params := passwordParams{ @@ -235,24 +154,23 @@ func (s solution) Part2(input io.Reader) (string, error) { close(inchan) -loop: for { select { case isMatch := <-reschan: if isMatch { count++ - } case <-donechan: operations-- } + if operations == 0 { - break loop + break } } close(reschan) close(donechan) - return strconv.Itoa(count), nil + return count, nil } diff --git a/internal/puzzles/solutions/2020/day02/solution_test.go b/internal/puzzles/solutions/2020/day02/solution_test.go index 35ce55b8..c9ee7286 100644 --- a/internal/puzzles/solutions/2020/day02/solution_test.go +++ b/internal/puzzles/solutions/2020/day02/solution_test.go @@ -9,10 +9,7 @@ import ( ) func Test_solution_Part1(t *testing.T) { - type fields struct { - year string - name string - } + var s solution type args struct { input io.Reader @@ -20,17 +17,12 @@ func Test_solution_Part1(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "test example from description", - fields: fields{ - year: year, - name: puzzleName, - }, args: args{ input: strings.NewReader("1-3 a: abcde\n1-3 b: cdefg\n2-9 c: ccccccccc"), }, @@ -43,10 +35,6 @@ func Test_solution_Part1(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } got, err := s.Part1(tt.args.input) if tt.wantErr { assert.Error(t, err) @@ -61,10 +49,7 @@ func Test_solution_Part1(t *testing.T) { } func Test_solution_Part2(t *testing.T) { - type fields struct { - year string - name string - } + var s solution type args struct { input io.Reader @@ -72,17 +57,12 @@ func Test_solution_Part2(t *testing.T) { tests := []struct { name string - fields fields args args want string wantErr bool }{ { name: "test example from description", - fields: fields{ - year: year, - name: puzzleName, - }, args: args{ input: strings.NewReader("1-3 a: abcde\n1-3 b: cdefg\n2-9 c: ccccccccc"), }, @@ -95,11 +75,6 @@ func Test_solution_Part2(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { - s := solution{ - year: tt.fields.year, - name: tt.fields.name, - } - got, err := s.Part2(tt.args.input) if tt.wantErr { assert.Error(t, err) diff --git a/internal/puzzles/solver.go b/internal/puzzles/solver.go index fbd0be37..d7333447 100644 --- a/internal/puzzles/solver.go +++ b/internal/puzzles/solver.go @@ -20,7 +20,7 @@ var ErrNotImplemented = errors.New("not implemented") type Solver interface { Part1(input io.Reader) (string, error) Part2(input io.Reader) (string, error) - Name() string + Day() string Year() string } @@ -34,7 +34,7 @@ var ( // it panics. func Register(solver Solver) { year := solver.Year() - name := solver.Name() + name := solver.Day() solversMu.Lock() defer solversMu.Unlock() @@ -69,8 +69,8 @@ func UnregisterAllSolvers(tb testing.TB) { solvers = make(map[string]map[string]Solver) } -// NamesByYear returns a sorted list of the names of the registered puzzle solvers for passed year. -func NamesByYear(year string) []string { +// DaysByYear returns a sorted list of the days of the registered puzzle solvers for passed year. +func DaysByYear(year string) []string { solversMu.RLock() defer solversMu.RUnlock() @@ -101,14 +101,14 @@ func GetYears() []string { return list } -// GetSolver returns registered solver by passed puzzle name. -func GetSolver(year string, name string) (Solver, error) { +// GetSolver returns registered solver by passed puzzle day. +func GetSolver(year, day string) (Solver, error) { if year == "" { return nil, errors.New("empty puzzle year") } - if name == "" { - return nil, errors.New("empty puzzle name") + if day == "" { + return nil, errors.New("empty puzzle day") } solversMu.Lock() @@ -120,9 +120,9 @@ func GetSolver(year string, name string) (Solver, error) { return nil, fmt.Errorf("unknown puzzle year [%s]", year) } - s, exist := solversYear[name] + s, exist := solversYear[day] if !exist { - return nil, fmt.Errorf("unknown puzzle name [%s]", name) + return nil, fmt.Errorf("unknown puzzle day [%s]", day) } return s, nil @@ -184,7 +184,7 @@ func Run(solver Solver, input io.Reader) (Result, error) { res := Result{ Year: solver.Year(), - Name: solver.Name(), + Name: solver.Day(), Part1: unsolved, Part2: unsolved, } diff --git a/internal/puzzles/solver_test.go b/internal/puzzles/solver_test.go index 640e44d3..0628953b 100644 --- a/internal/puzzles/solver_test.go +++ b/internal/puzzles/solver_test.go @@ -28,7 +28,7 @@ func (m mockSolver) Part2(_ io.Reader) (string, error) { return "part 2 of mockSolver", nil } -func (m mockSolver) Name() string { +func (m mockSolver) Day() string { return m.name } @@ -49,7 +49,7 @@ func (a anotherMockSolver) Part2(_ io.Reader) (string, error) { return "part 2 of anotherMockSolver", nil } -func (a anotherMockSolver) Name() string { +func (a anotherMockSolver) Day() string { return a.name } @@ -186,13 +186,71 @@ func TestSolversByYear(t *testing.T) { makeAndRegisterSolvers(t) - solvers := puzzles.NamesByYear("2019") + solvers := puzzles.DaysByYear("2019") expectedSolvers := []string{"mock", "anotherMock"} assert.ElementsMatch(t, expectedSolvers, solvers) - solvers = puzzles.NamesByYear("2017") + solvers = puzzles.DaysByYear("2017") expectedSolvers = []string{"mock1"} assert.ElementsMatch(t, expectedSolvers, solvers) } + +func TestResult_String(t *testing.T) { + type fields struct { + Year string + Name string + Part1 string + Part2 string + } + + tests := []struct { + name string + fields fields + want string + }{ + { + name: "", + fields: fields{ + Year: "2020", + Name: "day01", + Part1: "12", + Part2: "10", + }, + want: ` +2020/day01 puzzle answer: +| part1: 12 | +| part2: 10 | +`, + }, + { + name: "", + fields: fields{ + Year: "", + Name: "", + Part1: "", + Part2: "", + }, + want: ` +unknown/unknown puzzle answer: +| part1: not solved | +| part2: not solved | +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := puzzles.Result{ + Year: tt.fields.Year, + Name: tt.fields.Name, + Part1: tt.fields.Part1, + Part2: tt.fields.Part2, + } + + got := r.String() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/puzzles/utils/intcomputer/intcomputer.go b/internal/puzzles/utils/intcomputer/intcomputer.go index e10732a9..66e53487 100644 --- a/internal/puzzles/utils/intcomputer/intcomputer.go +++ b/internal/puzzles/utils/intcomputer/intcomputer.go @@ -32,15 +32,17 @@ const ( ) // New creates instance of IntComputer from passed intcode program. -func New(intcode io.Reader) (IntComputer, error) { +func New(in io.Reader) (IntComputer, error) { var c IntComputer - buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(intcode); err != nil { + var buf bytes.Buffer + if _, err := buf.ReadFrom(in); err != nil { return c, fmt.Errorf("failed to read: %w", err) } - nums := strings.Split(buf.String(), ",") + raw := strings.TrimSpace(buf.String()) + + nums := strings.Split(raw, ",") c.initial = make([]int, len(nums)) c.memory = make(map[int]int, len(nums)) @@ -139,7 +141,7 @@ func (c *IntComputer) abort() (int, error) { // Input allow to input noun and verb into intcode program for execution. // noun - 2nd position in intcode; // verb - 3rd position in intcode. -func (c *IntComputer) Input(noun int, verb int) { +func (c *IntComputer) Input(noun, verb int) { c.memory[1] = noun c.memory[2] = verb } diff --git a/internal/puzzles/year_string.go b/internal/puzzles/year_string.go new file mode 100644 index 00000000..0428a879 --- /dev/null +++ b/internal/puzzles/year_string.go @@ -0,0 +1,30 @@ +// Code generated by "stringer --type=Year --trimprefix=true --linecomment=true"; DO NOT EDIT. + +package puzzles + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[yearUnknown-0] + _ = x[Year2015-1] + _ = x[Year2016-2] + _ = x[Year2017-3] + _ = x[Year2018-4] + _ = x[Year2019-5] + _ = x[Year2020-6] + _ = x[yearSentinel-7] +} + +const _Year_name = "yearUnknown201520162017201820192020yearSentinel" + +var _Year_index = [...]uint8{0, 11, 15, 19, 23, 27, 31, 35, 47} + +func (i Year) String() string { + if i < 0 || i >= Year(len(_Year_index)-1) { + return "Year(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Year_name[_Year_index[i]:_Year_index[i+1]] +} diff --git a/internal/puzzles/year_string_test.go b/internal/puzzles/year_string_test.go new file mode 100644 index 00000000..7baaf1a4 --- /dev/null +++ b/internal/puzzles/year_string_test.go @@ -0,0 +1,35 @@ +package puzzles + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestYear_String(t *testing.T) { + const yearNotExist Year = 99 + + var tests = []struct { + name string + i Year + want string + }{ + { + name: "exist", + i: Year2017, + want: "2017", + }, + { + name: "not exist", + i: yearNotExist, + want: "Year(99)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.i.String() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/scripts/linting/golangci-pipeline.sh b/scripts/linting/golangci-pipeline.sh new file mode 100755 index 00000000..5404b62b --- /dev/null +++ b/scripts/linting/golangci-pipeline.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -Eeuo pipefail + +SCRIPT_NAME="$(basename "$0")" +SCRIPT_DIR="$(dirname "$0")" +REPO_ROOT="$(cd "${SCRIPT_DIR}" && git rev-parse --show-toplevel)" +SCRIPTS_DIR="${REPO_ROOT}/scripts" + +echo "${SCRIPT_NAME} is running... " + +source "${SCRIPTS_DIR}/linting/linters-source.sh" + +checkInstalled golangci-lint + +echo "Linting..." + +golangci-lint run --out-format=github-actions --no-config --disable-all -E govet +golangci-lint run --config .golangci.pipe.yml + +echo "${SCRIPT_NAME} done." diff --git a/scripts/linting/run-linters-pipeline.sh b/scripts/linting/golangci-sonar.sh similarity index 75% rename from scripts/linting/run-linters-pipeline.sh rename to scripts/linting/golangci-sonar.sh index 38059432..23e9d395 100755 --- a/scripts/linting/run-linters-pipeline.sh +++ b/scripts/linting/golangci-sonar.sh @@ -15,7 +15,6 @@ checkInstalled golangci-lint echo "Linting..." -golangci-lint run --no-config --disable-all -E govet -golangci-lint run --new-from-rev=HEAD~ --config .golangci.pipe.yml +golangci-lint run --config .golangci.yml > linters.out echo "${SCRIPT_NAME} done." diff --git a/scripts/linting/linters-source.sh b/scripts/linting/linters-source.sh index 35616eee..e78a0267 100755 --- a/scripts/linting/linters-source.sh +++ b/scripts/linting/linters-source.sh @@ -103,7 +103,7 @@ function golangci() { checkInstalled 'golangci-lint' - golangci-lint run --out-format=colored-line-number ./... + golangci-lint run --config .golangci.yml ./... echo "" } diff --git a/scripts/tests/coverage.sh b/scripts/tests/coverage.sh index 8368b59a..f126b3d5 100755 --- a/scripts/tests/coverage.sh +++ b/scripts/tests/coverage.sh @@ -19,7 +19,6 @@ export GO111MODULE=on rm -rf "${COVER_DIR}" mkdir -p "${COVER_DIR}" -go test --count=1 -tags=integration_test -coverprofile "${COVER_DIR}/integration.cov" -covermode=atomic ./... go test --count=1 -coverprofile "${COVER_DIR}/unit.cov" -covermode=atomic ./... { diff --git a/scripts/tests/sonar-report.sh b/scripts/tests/sonar-report.sh new file mode 100755 index 00000000..c8408106 --- /dev/null +++ b/scripts/tests/sonar-report.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -eu pipefail + +SCRIPT_NAME="$(basename "$0")" + +echo "${SCRIPT_NAME} is running... " + +go test -json ./... > tests-report.json + +echo "${SCRIPT_NAME} done." diff --git a/sonar-project.properties b/sonar-project.properties index 6ef8db72..bae91e52 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -14,6 +14,6 @@ sonar.exclusions=**/*_test.go,**/vendor/** sonar.tests=. sonar.test.inclusions=**/*_test.go -sonar.go.tests.reportPaths=tests.out +sonar.go.tests.reportPaths=tests-report.json sonar.go.coverage.reportPaths=./coverage/full.cov sonar.go.golangci-lint.reportPaths=linters.out \ No newline at end of file