Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implemented solution for puzzle 2023/day01 #284

Merged
merged 12 commits into from
Dec 1, 2023
14 changes: 14 additions & 0 deletions .github/workflows/readme-stars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ This repository contains solutions for puzzles and cli tool to run solutions to

## Implemented solutions

<!--- advent_readme_stars table [2023] --->

<!--- advent_readme_stars table [2022] --->
### 2022 Results

Expand Down
7 changes: 7 additions & 0 deletions deployments/docker-compose/go-tools-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ services:
service: tools
entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/run.sh'

run-tests-regression:
extends:
service: tools
entrypoint: /bin/sh -c 'git config --global --add safe.directory /app && ./scripts/tests/run-regression.sh'
environment:
AOC_SESSION: ${AOC_SESSION}

run-tests-coverage:
extends:
service: tools
Expand Down
1 change: 1 addition & 0 deletions internal/puzzles/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const (
Year2020 // 2020
Year2021 // 2021
Year2022 // 2022
Year2023 // 2023

yearSentinel
)
Expand Down
171 changes: 171 additions & 0 deletions internal/puzzles/solutions/2023/day01/solution.go
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)

Check warning on line 81 in internal/puzzles/solutions/2023/day01/solution.go

View check run for this annotation

Codecov / codecov/patch

internal/puzzles/solutions/2023/day01/solution.go#L81

Added line #L81 was not covered by tests
}

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)

Check warning on line 118 in internal/puzzles/solutions/2023/day01/solution.go

View check run for this annotation

Codecov / codecov/patch

internal/puzzles/solutions/2023/day01/solution.go#L118

Added line #L118 was not covered by tests
}

ok = true
case unicode.IsLetter(c):
word += string(c)

d, ok = getDigitFromWord(word, dict)
default:
word = ""

Check warning on line 127 in internal/puzzles/solutions/2023/day01/solution.go

View check run for this annotation

Codecov / codecov/patch

internal/puzzles/solutions/2023/day01/solution.go#L126-L127

Added lines #L126 - L127 were not covered by tests
}

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)

Check warning on line 149 in internal/puzzles/solutions/2023/day01/solution.go

View check run for this annotation

Codecov / codecov/patch

internal/puzzles/solutions/2023/day01/solution.go#L149

Added line #L149 was not covered by tests
}

return value, nil
}

func getDigitFromWord(word string, dict map[string]int) (int, bool) {
if word == "" {
return -1, false

Check warning on line 157 in internal/puzzles/solutions/2023/day01/solution.go

View check run for this annotation

Codecov / codecov/patch

internal/puzzles/solutions/2023/day01/solution.go#L157

Added line #L157 was not covered by tests
}

if len(dict) == 0 {
return -1, false
}

for s, i := range digitsDict {
if strings.Contains(word, s) {
return i, true
}
}

return -1, false
}
115 changes: 115 additions & 0 deletions internal/puzzles/solutions/2023/day01/solution_test.go
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)
})
}
}
Loading