-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implemented solution for puzzle 2023/day01 (#284)
* feat: Add constant for 2023 year * feat: Add puzzle description and skeleton * feat: Register puzzle 2023/day01 * feat: Add regression tests for 2023 year puzzles * chore: Add workflow configuration for 2023 * feat: Implement part 1 * feat: Implement part 2 * chore: Add regression tests * refactor: Reduce cyclo complexity * refactor: Remove redundant if * refactor: Simplify code
- Loading branch information
1 parent
c9cde2c
commit d69fb28
Showing
11 changed files
with
752 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,20 @@ jobs: | |
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Update 2023 year | ||
uses: k2bd/[email protected] | ||
env: | ||
YEAR: 2023 | ||
with: | ||
userId: ${{env.USER_ID}} | ||
leaderboardId: ${{env.BOARD_ID}} | ||
sessionCookie: ${{env.SESSION}} | ||
readmeLocation: ${{env.README}} | ||
headerPrefix: ${{env.HEADER_PFX}} | ||
year: ${{env.YEAR}} | ||
tableMarker: <!--- advent_readme_stars table [${{env.YEAR}}] ---> | ||
starSymbol: ${{env.STAR_SYMBOL}} | ||
|
||
- name: Update 2022 year | ||
uses: k2bd/[email protected] | ||
env: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,7 @@ const ( | |
Year2020 // 2020 | ||
Year2021 // 2021 | ||
Year2022 // 2022 | ||
Year2023 // 2023 | ||
|
||
yearSentinel | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Package day01 contains solution for https://adventofcode.com/2023/day/1 puzzle. | ||
package day01 | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"strconv" | ||
"strings" | ||
"unicode" | ||
|
||
"github.com/obalunenko/advent-of-code/internal/puzzles" | ||
) | ||
|
||
func init() { | ||
puzzles.Register(solution{}) | ||
} | ||
|
||
type solution struct{} | ||
|
||
func (s solution) Year() string { | ||
return puzzles.Year2023.String() | ||
} | ||
|
||
func (s solution) Day() string { | ||
return puzzles.Day01.String() | ||
} | ||
|
||
func (s solution) Part1(input io.Reader) (string, error) { | ||
sum, err := calibrate(input, nil) | ||
if err != nil { | ||
return "", fmt.Errorf("calibrating: %w", err) | ||
} | ||
|
||
return strconv.Itoa(sum), nil | ||
} | ||
|
||
func (s solution) Part2(input io.Reader) (string, error) { | ||
sum, err := calibrate(input, digitsDict) | ||
if err != nil { | ||
return "", fmt.Errorf("calibrating: %w", err) | ||
} | ||
|
||
return strconv.Itoa(sum), nil | ||
} | ||
|
||
const ( | ||
one = "one" | ||
two = "two" | ||
three = "three" | ||
four = "four" | ||
five = "five" | ||
six = "six" | ||
seven = "seven" | ||
eight = "eight" | ||
nine = "nine" | ||
) | ||
|
||
var digitsDict = map[string]int{ | ||
one: 1, | ||
two: 2, | ||
three: 3, | ||
four: 4, | ||
five: 5, | ||
six: 6, | ||
seven: 7, | ||
eight: 8, | ||
nine: 9, | ||
} | ||
|
||
func calibrate(input io.Reader, dictionary map[string]int) (int, error) { | ||
scanner := bufio.NewScanner(input) | ||
|
||
var values []int | ||
|
||
for scanner.Scan() { | ||
line := scanner.Text() | ||
|
||
value, err := extractNumberFromLine(line, dictionary) | ||
if err != nil { | ||
return 0, fmt.Errorf("extracting number from line %q: %w", line, err) | ||
} | ||
|
||
values = append(values, value) | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
return 0, fmt.Errorf("reading input: %w", err) | ||
} | ||
|
||
var sum int | ||
|
||
for _, v := range values { | ||
sum += v | ||
} | ||
|
||
return sum, nil | ||
} | ||
|
||
func extractNumberFromLine(line string, dict map[string]int) (int, error) { | ||
first, last := -1, -1 | ||
|
||
var word string | ||
|
||
for _, c := range line { | ||
var ( | ||
d int | ||
ok bool | ||
err error | ||
) | ||
|
||
switch { | ||
case unicode.IsDigit(c): | ||
word = " " | ||
|
||
d, err = strconv.Atoi(string(c)) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to convert %q to int: %w", string(c), err) | ||
} | ||
|
||
ok = true | ||
case unicode.IsLetter(c): | ||
word += string(c) | ||
|
||
d, ok = getDigitFromWord(word, dict) | ||
default: | ||
word = "" | ||
} | ||
|
||
if !ok { | ||
continue | ||
} | ||
|
||
word = word[len(word)-1:] | ||
|
||
if first == -1 { | ||
first = d | ||
} else { | ||
last = d | ||
} | ||
} | ||
|
||
if last == -1 { | ||
last = first | ||
} | ||
|
||
value, err := strconv.Atoi(strconv.Itoa(first) + strconv.Itoa(last)) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to convert %d%d to int: %w", first, last, err) | ||
} | ||
|
||
return value, nil | ||
} | ||
|
||
func getDigitFromWord(word string, dict map[string]int) (int, bool) { | ||
if word == "" { | ||
return -1, false | ||
} | ||
|
||
if len(dict) == 0 { | ||
return -1, false | ||
} | ||
|
||
for s, i := range digitsDict { | ||
if strings.Contains(word, s) { | ||
return i, true | ||
} | ||
} | ||
|
||
return -1, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package day01 | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"strings" | ||
"testing" | ||
"testing/iotest" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_solution_Year(t *testing.T) { | ||
var s solution | ||
|
||
want := "2023" | ||
got := s.Year() | ||
|
||
assert.Equal(t, want, got) | ||
} | ||
|
||
func Test_solution_Day(t *testing.T) { | ||
var s solution | ||
|
||
want := "1" | ||
got := s.Day() | ||
|
||
assert.Equal(t, want, got) | ||
} | ||
|
||
func Test_solution_Part1(t *testing.T) { | ||
var s solution | ||
|
||
type args struct { | ||
input io.Reader | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr assert.ErrorAssertionFunc | ||
}{ | ||
{ | ||
name: "test example from description", | ||
args: args{ | ||
input: strings.NewReader("1abc2\npqr3stu8vwx\na1b2c3d4e5f\ntreb7uchet"), | ||
}, | ||
want: "142", | ||
wantErr: assert.NoError, | ||
}, | ||
{ | ||
name: "", | ||
args: args{ | ||
input: iotest.ErrReader(errors.New("custom error")), | ||
}, | ||
want: "", | ||
wantErr: assert.Error, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := s.Part1(tt.args.input) | ||
if !tt.wantErr(t, err) { | ||
return | ||
} | ||
|
||
assert.Equal(t, tt.want, got) | ||
}) | ||
} | ||
} | ||
|
||
func Test_solution_Part2(t *testing.T) { | ||
var s solution | ||
|
||
type args struct { | ||
input io.Reader | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr assert.ErrorAssertionFunc | ||
}{ | ||
{ | ||
name: "", | ||
args: args{ | ||
input: strings.NewReader("two1nine\neightwothree\nabcone2threexyz\nxtwone3four\n4nineeightseven2\nzoneight234\n7pqrstsixteen"), | ||
}, | ||
want: "281", | ||
wantErr: assert.NoError, | ||
}, | ||
{ | ||
name: "", | ||
args: args{ | ||
input: iotest.ErrReader(errors.New("custom error")), | ||
}, | ||
want: "", | ||
wantErr: assert.Error, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := s.Part2(tt.args.input) | ||
if !tt.wantErr(t, err) { | ||
return | ||
} | ||
|
||
assert.Equal(t, tt.want, got) | ||
}) | ||
} | ||
} |
Oops, something went wrong.