Skip to content

Commit c7f4ea7

Browse files
authored
Implement 2021/day07 puzzle solution (#73)
* feat(2021/day07): Add spec and tests * tests(2021/day07): Update regression tests * feat(2021/day07): Add negative tests * feat: Implement parse ints utils function * feat(2021/day07): Implement part1 * feat(2021/day07): Register solver * tests: Add regression tests for 2021/day07 part1 * feat(2021/day07): Part 2 implementation * feat(2021/day07): Add fixme * feat(2021/day07): Implement part 2 * tests: Add regression tests * refactor: Fix wcl linter warnings * refactor: Remove not needed code * tests: Improve test coverage * tests: Add edge case test
1 parent f48f177 commit c7f4ea7

File tree

7 files changed

+771
-3
lines changed

7 files changed

+771
-3
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package utils
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"strconv"
8+
"strings"
9+
)
10+
11+
// ParseInts parses io.Reader into []int.
12+
func ParseInts(in io.Reader, sep string) ([]int, error) {
13+
scanner := bufio.NewScanner(in)
14+
15+
var res []int
16+
17+
for scanner.Scan() {
18+
line := scanner.Text()
19+
20+
if sep == "" {
21+
n, err := strconv.Atoi(line)
22+
if err != nil {
23+
return nil, fmt.Errorf("parse int: %w", err)
24+
}
25+
26+
res = append(res, n)
27+
} else {
28+
split := strings.Split(line, sep)
29+
30+
for _, s := range split {
31+
n, err := strconv.Atoi(s)
32+
if err != nil {
33+
return nil, fmt.Errorf("parse int: %w", err)
34+
}
35+
36+
res = append(res, n)
37+
}
38+
}
39+
}
40+
41+
if err := scanner.Err(); err != nil {
42+
return nil, fmt.Errorf("scanner error: %w", err)
43+
}
44+
45+
return res, nil
46+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package utils
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 TestParseInts(t *testing.T) {
14+
type args struct {
15+
in io.Reader
16+
sep string
17+
}
18+
19+
tests := []struct {
20+
name string
21+
args args
22+
want []int
23+
wantErr assert.ErrorAssertionFunc
24+
}{
25+
{
26+
name: "",
27+
args: args{
28+
in: strings.NewReader("1,2,3,4,5"),
29+
sep: ",",
30+
},
31+
want: []int{1, 2, 3, 4, 5},
32+
wantErr: assert.NoError,
33+
},
34+
{
35+
name: "",
36+
args: args{
37+
in: strings.NewReader("1\n2\n3\n4\n5"),
38+
sep: "",
39+
},
40+
want: []int{1, 2, 3, 4, 5},
41+
wantErr: assert.NoError,
42+
},
43+
{
44+
name: "",
45+
args: args{
46+
in: strings.NewReader("1,2\n3,4\n5"),
47+
sep: ",",
48+
},
49+
want: []int{1, 2, 3, 4, 5},
50+
wantErr: assert.NoError,
51+
},
52+
{
53+
name: "",
54+
args: args{
55+
in: iotest.ErrReader(errors.New("custom error")),
56+
sep: ",",
57+
},
58+
want: nil,
59+
wantErr: assert.Error,
60+
},
61+
{
62+
name: "broken int",
63+
args: args{
64+
in: strings.NewReader("1s,2\n3,4\n5"),
65+
sep: ",",
66+
},
67+
want: nil,
68+
wantErr: assert.Error,
69+
},
70+
{
71+
name: "broken int",
72+
args: args{
73+
in: strings.NewReader("1,\n2\n3\n4\n5"),
74+
sep: "",
75+
},
76+
want: nil,
77+
wantErr: assert.Error,
78+
},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
got, err := ParseInts(tt.args.in, tt.args.sep)
84+
if !tt.wantErr(t, err) {
85+
return
86+
}
87+
88+
assert.Equal(t, tt.want, got)
89+
})
90+
}
91+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Package day07 contains solution for https://adventofcode.com/2021/day/7 puzzle.
2+
package day07
3+
4+
import (
5+
"fmt"
6+
"io"
7+
"sort"
8+
"strconv"
9+
10+
"github.com/obalunenko/advent-of-code/internal/puzzles"
11+
"github.com/obalunenko/advent-of-code/internal/puzzles/common/utils"
12+
)
13+
14+
func init() {
15+
puzzles.Register(solution{})
16+
}
17+
18+
type solution struct{}
19+
20+
func (solution) Day() string {
21+
return puzzles.Day07.String()
22+
}
23+
24+
func (solution) Year() string {
25+
return puzzles.Year2021.String()
26+
}
27+
28+
func (solution) Part1(input io.Reader) (string, error) {
29+
crabs, err := getCrabs(input)
30+
if err != nil {
31+
return "", fmt.Errorf("get crabs: %w", err)
32+
}
33+
34+
s := makeSwarm(crabs)
35+
36+
s.calcDistances(part1Cost)
37+
38+
cost := s.minDistanceCost()
39+
40+
return strconv.Itoa(cost), nil
41+
}
42+
43+
func (solution) Part2(input io.Reader) (string, error) {
44+
crabs, err := getCrabs(input)
45+
if err != nil {
46+
return "", fmt.Errorf("get crabs: %w", err)
47+
}
48+
49+
s := makeSwarm(crabs)
50+
51+
s.calcDistances(part2Cost)
52+
53+
cost := s.minDistanceCost()
54+
55+
return strconv.Itoa(cost), nil
56+
}
57+
58+
func getCrabs(input io.Reader) ([]int, error) {
59+
crabs, err := utils.ParseInts(input, ",")
60+
if err != nil {
61+
return nil, fmt.Errorf("parse int slice from reader: %w", err)
62+
}
63+
64+
return crabs, nil
65+
}
66+
67+
const (
68+
undef = -99999
69+
)
70+
71+
func makeMatrix(crabs []int) [][]int {
72+
const (
73+
header = 1
74+
)
75+
76+
sort.Ints(crabs)
77+
78+
cnum := len(crabs)
79+
80+
max := crabs[cnum-1]
81+
82+
matrix := make([][]int, cnum+header)
83+
84+
// matrix[i][j].
85+
// i - crabs; j - all positions from 0 to max
86+
for i := 0; i < cnum+header; i++ {
87+
matrix[i] = make([]int, max+header+1)
88+
89+
if i == 0 {
90+
matrix[i][0] = undef
91+
92+
for j := 1; j <= max+header; j++ {
93+
matrix[i][j] = j - 1
94+
}
95+
96+
continue
97+
}
98+
99+
matrix[i][0] = crabs[i-1]
100+
}
101+
102+
return matrix
103+
}
104+
105+
type swarm struct {
106+
crabsNum int
107+
distancesNum int
108+
crabsMatrix [][]int
109+
}
110+
111+
func (s swarm) getMatrixILen() int {
112+
return s.crabsNum + 1
113+
}
114+
115+
func (s swarm) getMatrixJLen() int {
116+
return s.distancesNum + 1
117+
}
118+
119+
func makeSwarm(crabs []int) swarm {
120+
matrix := makeMatrix(crabs)
121+
122+
crabsNum := len(matrix)
123+
124+
distNum := len(matrix[0])
125+
126+
return swarm{
127+
crabsNum: crabsNum - 1,
128+
distancesNum: distNum - 1,
129+
crabsMatrix: matrix,
130+
}
131+
}
132+
133+
type fuelCostFunc func(p int) int
134+
135+
func part1Cost(p int) int {
136+
return p
137+
}
138+
139+
func part2Cost(p int) int {
140+
// formula a_{n}=a_{1}+(n-1)d
141+
an := 1 + 1*(p-1)
142+
143+
// formula s_{n}=(a_{1}+a_{n})/2*n
144+
s := ((1 + an) * p) / 2
145+
146+
return s
147+
}
148+
149+
func (s *swarm) calcDistances(cost fuelCostFunc) {
150+
for i := 1; i < s.getMatrixILen(); i++ {
151+
for j := 1; j < s.getMatrixJLen(); j++ {
152+
p := s.crabsMatrix[i][0] - s.crabsMatrix[0][j]
153+
if p < 0 {
154+
p *= -1
155+
}
156+
157+
s.crabsMatrix[i][j] = cost(p)
158+
}
159+
}
160+
}
161+
162+
func (s swarm) minDistanceCost() int {
163+
return minDistanceCost(s.crabsMatrix)
164+
}
165+
166+
func minDistanceCost(matrix [][]int) int {
167+
var min int
168+
169+
ilen := len(matrix)
170+
jlen := len(matrix[0])
171+
172+
for j := 1; j < jlen; j++ {
173+
var f int
174+
175+
for i := 1; i < ilen; i++ {
176+
f += matrix[i][j]
177+
}
178+
179+
if j == 1 {
180+
min = f
181+
}
182+
183+
if f < min {
184+
min = f
185+
}
186+
}
187+
188+
return min
189+
}

0 commit comments

Comments
 (0)