Skip to content

Commit d69fb28

Browse files
authored
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
1 parent c9cde2c commit d69fb28

File tree

11 files changed

+752
-3
lines changed

11 files changed

+752
-3
lines changed

.github/workflows/readme-stars.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ jobs:
2424
- name: Checkout
2525
uses: actions/checkout@v4
2626

27+
- name: Update 2023 year
28+
uses: k2bd/advent-readme-stars@v1.0.3
29+
env:
30+
YEAR: 2023
31+
with:
32+
userId: ${{env.USER_ID}}
33+
leaderboardId: ${{env.BOARD_ID}}
34+
sessionCookie: ${{env.SESSION}}
35+
readmeLocation: ${{env.README}}
36+
headerPrefix: ${{env.HEADER_PFX}}
37+
year: ${{env.YEAR}}
38+
tableMarker: <!--- advent_readme_stars table [${{env.YEAR}}] --->
39+
starSymbol: ${{env.STAR_SYMBOL}}
40+
2741
- name: Update 2022 year
2842
uses: k2bd/advent-readme-stars@v1.0.3
2943
env:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This repository contains solutions for puzzles and cli tool to run solutions to
2727

2828
## Implemented solutions
2929

30+
<!--- advent_readme_stars table [2023] --->
31+
3032
<!--- advent_readme_stars table [2022] --->
3133
### 2022 Results
3234

deployments/docker-compose/go-tools-docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ services:
3030
service: tools
3131
entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/run.sh'
3232

33+
run-tests-regression:
34+
extends:
35+
service: tools
36+
entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/run-regression.sh'
37+
environment:
38+
AOC_SESSION: ${AOC_SESSION}
39+
3340
run-tests-coverage:
3441
extends:
3542
service: tools

internal/puzzles/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const (
5353
Year2020 // 2020
5454
Year2021 // 2021
5555
Year2022 // 2022
56+
Year2023 // 2023
5657

5758
yearSentinel
5859
)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Package day01 contains solution for https://adventofcode.com/2023/day/1 puzzle.
2+
package day01
3+
4+
import (
5+
"bufio"
6+
"fmt"
7+
"io"
8+
"strconv"
9+
"strings"
10+
"unicode"
11+
12+
"github.com/obalunenko/advent-of-code/internal/puzzles"
13+
)
14+
15+
func init() {
16+
puzzles.Register(solution{})
17+
}
18+
19+
type solution struct{}
20+
21+
func (s solution) Year() string {
22+
return puzzles.Year2023.String()
23+
}
24+
25+
func (s solution) Day() string {
26+
return puzzles.Day01.String()
27+
}
28+
29+
func (s solution) Part1(input io.Reader) (string, error) {
30+
sum, err := calibrate(input, nil)
31+
if err != nil {
32+
return "", fmt.Errorf("calibrating: %w", err)
33+
}
34+
35+
return strconv.Itoa(sum), nil
36+
}
37+
38+
func (s solution) Part2(input io.Reader) (string, error) {
39+
sum, err := calibrate(input, digitsDict)
40+
if err != nil {
41+
return "", fmt.Errorf("calibrating: %w", err)
42+
}
43+
44+
return strconv.Itoa(sum), nil
45+
}
46+
47+
const (
48+
one = "one"
49+
two = "two"
50+
three = "three"
51+
four = "four"
52+
five = "five"
53+
six = "six"
54+
seven = "seven"
55+
eight = "eight"
56+
nine = "nine"
57+
)
58+
59+
var digitsDict = map[string]int{
60+
one: 1,
61+
two: 2,
62+
three: 3,
63+
four: 4,
64+
five: 5,
65+
six: 6,
66+
seven: 7,
67+
eight: 8,
68+
nine: 9,
69+
}
70+
71+
func calibrate(input io.Reader, dictionary map[string]int) (int, error) {
72+
scanner := bufio.NewScanner(input)
73+
74+
var values []int
75+
76+
for scanner.Scan() {
77+
line := scanner.Text()
78+
79+
value, err := extractNumberFromLine(line, dictionary)
80+
if err != nil {
81+
return 0, fmt.Errorf("extracting number from line %q: %w", line, err)
82+
}
83+
84+
values = append(values, value)
85+
}
86+
87+
if err := scanner.Err(); err != nil {
88+
return 0, fmt.Errorf("reading input: %w", err)
89+
}
90+
91+
var sum int
92+
93+
for _, v := range values {
94+
sum += v
95+
}
96+
97+
return sum, nil
98+
}
99+
100+
func extractNumberFromLine(line string, dict map[string]int) (int, error) {
101+
first, last := -1, -1
102+
103+
var word string
104+
105+
for _, c := range line {
106+
var (
107+
d int
108+
ok bool
109+
err error
110+
)
111+
112+
switch {
113+
case unicode.IsDigit(c):
114+
word = " "
115+
116+
d, err = strconv.Atoi(string(c))
117+
if err != nil {
118+
return 0, fmt.Errorf("failed to convert %q to int: %w", string(c), err)
119+
}
120+
121+
ok = true
122+
case unicode.IsLetter(c):
123+
word += string(c)
124+
125+
d, ok = getDigitFromWord(word, dict)
126+
default:
127+
word = ""
128+
}
129+
130+
if !ok {
131+
continue
132+
}
133+
134+
word = word[len(word)-1:]
135+
136+
if first == -1 {
137+
first = d
138+
} else {
139+
last = d
140+
}
141+
}
142+
143+
if last == -1 {
144+
last = first
145+
}
146+
147+
value, err := strconv.Atoi(strconv.Itoa(first) + strconv.Itoa(last))
148+
if err != nil {
149+
return 0, fmt.Errorf("failed to convert %d%d to int: %w", first, last, err)
150+
}
151+
152+
return value, nil
153+
}
154+
155+
func getDigitFromWord(word string, dict map[string]int) (int, bool) {
156+
if word == "" {
157+
return -1, false
158+
}
159+
160+
if len(dict) == 0 {
161+
return -1, false
162+
}
163+
164+
for s, i := range digitsDict {
165+
if strings.Contains(word, s) {
166+
return i, true
167+
}
168+
}
169+
170+
return -1, false
171+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package day01
2+
3+
import (
4+
"errors"
5+
"io"
6+
"strings"
7+
"testing"
8+
"testing/iotest"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_solution_Year(t *testing.T) {
14+
var s solution
15+
16+
want := "2023"
17+
got := s.Year()
18+
19+
assert.Equal(t, want, got)
20+
}
21+
22+
func Test_solution_Day(t *testing.T) {
23+
var s solution
24+
25+
want := "1"
26+
got := s.Day()
27+
28+
assert.Equal(t, want, got)
29+
}
30+
31+
func Test_solution_Part1(t *testing.T) {
32+
var s solution
33+
34+
type args struct {
35+
input io.Reader
36+
}
37+
38+
tests := []struct {
39+
name string
40+
args args
41+
want string
42+
wantErr assert.ErrorAssertionFunc
43+
}{
44+
{
45+
name: "test example from description",
46+
args: args{
47+
input: strings.NewReader("1abc2\npqr3stu8vwx\na1b2c3d4e5f\ntreb7uchet"),
48+
},
49+
want: "142",
50+
wantErr: assert.NoError,
51+
},
52+
{
53+
name: "",
54+
args: args{
55+
input: iotest.ErrReader(errors.New("custom error")),
56+
},
57+
want: "",
58+
wantErr: assert.Error,
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
got, err := s.Part1(tt.args.input)
65+
if !tt.wantErr(t, err) {
66+
return
67+
}
68+
69+
assert.Equal(t, tt.want, got)
70+
})
71+
}
72+
}
73+
74+
func Test_solution_Part2(t *testing.T) {
75+
var s solution
76+
77+
type args struct {
78+
input io.Reader
79+
}
80+
81+
tests := []struct {
82+
name string
83+
args args
84+
want string
85+
wantErr assert.ErrorAssertionFunc
86+
}{
87+
{
88+
name: "",
89+
args: args{
90+
input: strings.NewReader("two1nine\neightwothree\nabcone2threexyz\nxtwone3four\n4nineeightseven2\nzoneight234\n7pqrstsixteen"),
91+
},
92+
want: "281",
93+
wantErr: assert.NoError,
94+
},
95+
{
96+
name: "",
97+
args: args{
98+
input: iotest.ErrReader(errors.New("custom error")),
99+
},
100+
want: "",
101+
wantErr: assert.Error,
102+
},
103+
}
104+
105+
for _, tt := range tests {
106+
t.Run(tt.name, func(t *testing.T) {
107+
got, err := s.Part2(tt.args.input)
108+
if !tt.wantErr(t, err) {
109+
return
110+
}
111+
112+
assert.Equal(t, tt.want, got)
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)