Skip to content

Commit cdf85fc

Browse files
authored
feat(puzzles): Implement solution for 2015/day03 (#87)
* feat(puzzles): Add spec; skeleton and tests for 2015/day03 * feat(puzzles): Register 2015/day03 solver * feat(puzzles): Implement 2015/day03 part1 * tests: Update regression tests * feat(puzzles): Add spec for 2015/day03 part2 * feat(puzzles): Add tests for 2015/day03 part2 * feat(puzzles): Implement 2015/day03 part2 * style: Fix fmt * refactor(puzzles): Refactor 2015/day03 * tests: Update regression tests
1 parent a70d7ee commit cdf85fc

File tree

5 files changed

+392
-3
lines changed

5 files changed

+392
-3
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Package day03 contains solution for https://adventofcode.com/2015/day/3 puzzle.
2+
package day03
3+
4+
import (
5+
"bufio"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"strconv"
10+
11+
"github.com/obalunenko/advent-of-code/internal/puzzles"
12+
)
13+
14+
func init() {
15+
puzzles.Register(solution{})
16+
}
17+
18+
type solution struct{}
19+
20+
func (solution) Day() string {
21+
return puzzles.Day03.String()
22+
}
23+
24+
func (solution) Year() string {
25+
return puzzles.Year2015.String()
26+
}
27+
28+
func (solution) Part1(input io.Reader) (string, error) {
29+
const santaNum = 1
30+
31+
return solve(input, santaNum)
32+
}
33+
34+
func (solution) Part2(input io.Reader) (string, error) {
35+
const santaNum = 2
36+
37+
return solve(input, santaNum)
38+
}
39+
40+
func solve(input io.Reader, santaNum int) (string, error) {
41+
addresses, err := makeAddressesList(input)
42+
if err != nil {
43+
return "", fmt.Errorf("make addresses: %w", err)
44+
}
45+
46+
deliverymen := make([]deliveryman, 0, santaNum)
47+
48+
for i := 0; i < santaNum; i++ {
49+
deliverymen = append(deliverymen, newSanta())
50+
}
51+
52+
delivery := newSantaDelivery(deliverymen)
53+
54+
if err = delivery.deliver(addresses); err != nil {
55+
return "", fmt.Errorf("deliver: %w", err)
56+
}
57+
58+
visited := delivery.housesVisited()
59+
60+
return strconv.Itoa(visited), nil
61+
}
62+
63+
func makeAddressesList(input io.Reader) ([]string, error) {
64+
reader := bufio.NewReader(input)
65+
66+
var list []string
67+
68+
for {
69+
r, _, err := reader.ReadRune()
70+
if err != nil {
71+
if errors.Is(err, io.EOF) {
72+
break
73+
}
74+
75+
return nil, fmt.Errorf("read rune: %w", err)
76+
}
77+
78+
if r == '\n' {
79+
continue
80+
}
81+
82+
list = append(list, string(r))
83+
}
84+
85+
return list, nil
86+
}
87+
88+
type grid struct {
89+
x, y int
90+
}
91+
92+
func newGrid() grid {
93+
return grid{
94+
x: 0,
95+
y: 0,
96+
}
97+
}
98+
99+
var errUnknownDirection = errors.New("unknown direction")
100+
101+
func (g *grid) move(m string) error {
102+
const (
103+
north = "^"
104+
south = "v"
105+
east = ">"
106+
west = "<"
107+
)
108+
109+
switch m {
110+
case north:
111+
g.y++
112+
case south:
113+
g.y--
114+
case west:
115+
g.x--
116+
case east:
117+
g.x++
118+
default:
119+
return errUnknownDirection
120+
}
121+
122+
return nil
123+
}
124+
125+
type santaDelivery struct {
126+
santas []deliveryman
127+
}
128+
129+
func newSantaDelivery(deliverymen []deliveryman) santaDelivery {
130+
return santaDelivery{
131+
santas: deliverymen,
132+
}
133+
}
134+
135+
func (s *santaDelivery) deliver(addresses []string) error {
136+
for i, address := range addresses {
137+
snt := s.santas[0]
138+
139+
if i%2 == 0 && len(s.santas) == 2 {
140+
snt = s.santas[1]
141+
}
142+
143+
if err := snt.visit(address); err != nil {
144+
return err
145+
}
146+
}
147+
148+
return nil
149+
}
150+
151+
func (s *santaDelivery) housesVisited() int {
152+
total := make(map[grid]int)
153+
154+
for i := range s.santas {
155+
snt := s.santas[i]
156+
157+
for address, count := range snt.housesVisited() {
158+
total[address] += count
159+
}
160+
}
161+
162+
return len(total)
163+
}
164+
165+
type deliveryman interface {
166+
visit(address string) error
167+
housesVisited() map[grid]int
168+
}
169+
170+
type santa struct {
171+
location grid
172+
visited map[grid]int
173+
}
174+
175+
func newSanta() *santa {
176+
startLoc := newGrid()
177+
178+
visited := make(map[grid]int)
179+
visited[startLoc]++
180+
181+
return &santa{
182+
location: startLoc,
183+
visited: visited,
184+
}
185+
}
186+
187+
func (s *santa) visit(address string) error {
188+
if err := s.location.move(address); err != nil {
189+
return fmt.Errorf("failed to visit address: %w", err)
190+
}
191+
192+
s.visited[s.location]++
193+
194+
return nil
195+
}
196+
197+
func (s santa) housesVisited() map[grid]int {
198+
return s.visited
199+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package day03
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 := "2015"
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 := "3"
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 bool
43+
}{
44+
{
45+
name: "> delivers presents to 2 houses: one at the starting location, and one to the east.",
46+
args: args{
47+
input: strings.NewReader(">\n"),
48+
},
49+
want: "2",
50+
wantErr: false,
51+
},
52+
{
53+
name: "^>v< delivers presents to 4 houses in a square, including twice to the house at his starting/ending location.",
54+
args: args{
55+
input: strings.NewReader("^>v<\n"),
56+
},
57+
want: "4",
58+
wantErr: false,
59+
},
60+
{
61+
name: "^v^v^v^v^v delivers a bunch of presents to some very lucky children at only 2 houses.",
62+
args: args{
63+
input: strings.NewReader("^v^v^v^v^v\n"),
64+
},
65+
want: "2",
66+
wantErr: false,
67+
},
68+
{
69+
name: "",
70+
args: args{
71+
input: iotest.ErrReader(errors.New("custom error")),
72+
},
73+
want: "",
74+
wantErr: true,
75+
},
76+
}
77+
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
got, err := s.Part1(tt.args.input)
81+
if tt.wantErr {
82+
assert.Error(t, err)
83+
84+
return
85+
}
86+
87+
assert.NoError(t, err)
88+
assert.Equal(t, tt.want, got)
89+
})
90+
}
91+
}
92+
93+
func Test_solution_Part2(t *testing.T) {
94+
var s solution
95+
96+
type args struct {
97+
input io.Reader
98+
}
99+
100+
tests := []struct {
101+
name string
102+
args args
103+
want string
104+
wantErr bool
105+
}{
106+
{
107+
name: "^v delivers presents to 3 houses, because Santa goes north, and then Robo-Santa goes south.",
108+
args: args{
109+
input: strings.NewReader("^v\n"),
110+
},
111+
want: "3",
112+
wantErr: false,
113+
},
114+
{
115+
name: "^>v< now delivers presents to 3 houses, and Santa and Robo-Santa end up back where they started.",
116+
args: args{
117+
input: strings.NewReader("^>v<\n"),
118+
},
119+
want: "3",
120+
wantErr: false,
121+
},
122+
{
123+
name: "^v^v^v^v^v now delivers presents to 11 houses, with Santa going one direction and Robo-Santa going the other.",
124+
args: args{
125+
input: strings.NewReader("^v^v^v^v^v\n"),
126+
},
127+
want: "11",
128+
wantErr: false,
129+
},
130+
{
131+
name: "",
132+
args: args{
133+
input: iotest.ErrReader(errors.New("custom error")),
134+
},
135+
want: "",
136+
wantErr: true,
137+
},
138+
}
139+
140+
for _, tt := range tests {
141+
t.Run(tt.name, func(t *testing.T) {
142+
got, err := s.Part2(tt.args.input)
143+
if tt.wantErr {
144+
assert.Error(t, err)
145+
146+
return
147+
}
148+
149+
assert.NoError(t, err)
150+
assert.Equal(t, tt.want, got)
151+
})
152+
}
153+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# --- Day 3: Perfectly Spherical Houses in a Vacuum ---
2+
3+
## --- Part One ---
4+
5+
Santa is delivering presents to an infinite two-dimensional grid of houses.
6+
7+
He begins by delivering a present to the house at his starting location, and then an elf at the North Pole calls
8+
him via radio and tells him where to move next. Moves are always exactly one house to the north (`^`), south (`v`),
9+
east (`>`), or west (`<`). After each move, he delivers another present to the house at his new location.
10+
11+
However, the elf back at the North Pole has had a little too much eggnog, and so his directions are a little off,
12+
and Santa ends up visiting some houses more than once. How many houses receive at least one present?
13+
14+
### For example:
15+
16+
- `>` delivers presents to `2` houses: one at the starting location, and one to the east.
17+
- `^>v<` delivers presents to `4` houses in a square, including twice to the house at his starting/ending location.
18+
- `^v^v^v^v^v` delivers a bunch of presents to some very lucky children at only 2 houses.
19+
20+
## --- Part Two ---
21+
22+
The next year, to speed up the process, Santa creates a robot version of himself, Robo-Santa, to deliver presents with
23+
him.
24+
25+
Santa and Robo-Santa start at the same location (delivering two presents to the same starting house),
26+
then take turns moving based on instructions from the elf, who is eggnoggedly reading from the same script as the
27+
previous year.
28+
29+
This year, how many houses receive at least one present?
30+
31+
### For example:
32+
33+
- `^v` delivers presents to `3` houses, because Santa goes north, and then Robo-Santa goes south.
34+
- `^>v<` now delivers presents to `3` houses, and Santa and Robo-Santa end up back where they started.
35+
- `^v^v^v^v^v` now delivers presents to `11` houses, with Santa going one direction and Robo-Santa going the other.

internal/puzzles/solutions/register_2015.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ import (
88
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2015/day01"
99
// register day02 solution.
1010
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2015/day02"
11+
// register day03 solution.
12+
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2015/day03"
1113
)

0 commit comments

Comments
 (0)