Skip to content

Commit

Permalink
feat: Implemented solution for puzzle 2023/day01 (#284)
Browse files Browse the repository at this point in the history
* 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
obalunenko authored Dec 1, 2023
1 parent c9cde2c commit d69fb28
Show file tree
Hide file tree
Showing 11 changed files with 752 additions and 3 deletions.
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)
}

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
}
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

0 comments on commit d69fb28

Please sign in to comment.