Skip to content

Commit 2087108

Browse files
authored
Implement solution for 2021/day05 puzzle (#67)
* feat: Add tests and spec for 2021/day05 puzzle * feat: Add testdata for 2021/day05 * feat: Implement 2021/day05 part1 * feat: Add spec and tests for 2021/day05 part2 * feat: Implement 2021/day05 part2 * tests: Update regression tests * fix: Sonar linter bugs * style: Fmt
1 parent 042ecf5 commit 2087108

File tree

10 files changed

+1347
-5
lines changed

10 files changed

+1347
-5
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ _Implemented solution for puzzle https://adventofcode.com/{year}/day/{day}_
44

55
## Github Issue
66

7-
_If any, add a JIRA ticket number._
7+
_If any, add a GitHub issue number._
88

99
## Possible failures/side-effects
1010

internal/puzzles/input/content.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
)
1616

1717
var (
18-
1918
// ErrNotFound returns when puzzle input is not yet unlocked or invalid date passed.
2019
ErrNotFound = errors.New("puzzle inout not found")
2120
// ErrUnauthorized returns when session is empty or invalid.
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
// Package day05 contains solution for https://adventofcode.com/2021/day/5 puzzle.
2+
package day05
3+
4+
import (
5+
"bufio"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"math"
10+
"regexp"
11+
"strconv"
12+
"strings"
13+
14+
"github.com/obalunenko/advent-of-code/internal/puzzles"
15+
)
16+
17+
func init() {
18+
puzzles.Register(solution{})
19+
}
20+
21+
type solution struct{}
22+
23+
func (s solution) Year() string {
24+
return puzzles.Year2021.String()
25+
}
26+
27+
func (s solution) Day() string {
28+
return puzzles.Day05.String()
29+
}
30+
31+
func (s solution) Part1(input io.Reader) (string, error) {
32+
lines, err := getLines(input)
33+
if err != nil {
34+
return "", fmt.Errorf("get lines: %w", err)
35+
}
36+
37+
lines = filterLines(lines, part1Filter)
38+
39+
d := drawDiagram(lines)
40+
41+
zones := d.dangerZones(isDangerZone)
42+
43+
return strconv.Itoa(zones), nil
44+
}
45+
46+
func (s solution) Part2(input io.Reader) (string, error) {
47+
lines, err := getLines(input)
48+
if err != nil {
49+
return "", fmt.Errorf("get lines: %w", err)
50+
}
51+
52+
lines = filterLines(lines, part2Filter)
53+
54+
d := drawDiagram(lines)
55+
56+
zones := d.dangerZones(isDangerZone)
57+
58+
return strconv.Itoa(zones), nil
59+
}
60+
61+
type position struct {
62+
x, y int
63+
}
64+
65+
var reg = regexp.MustCompile(`(?s)\d+,\d+`)
66+
67+
func parseCoordinates(s string) (position, error) {
68+
const (
69+
cNum = 2
70+
delim = ","
71+
xpos = 0
72+
ypos = 1
73+
)
74+
75+
spl := strings.Split(s, delim)
76+
if len(spl) != cNum {
77+
return position{}, errors.New("wrong coordinates pair")
78+
}
79+
80+
x, err := strconv.Atoi(spl[xpos])
81+
if err != nil {
82+
return position{}, fmt.Errorf("parse x to int: %w", err)
83+
}
84+
85+
y, err := strconv.Atoi(spl[ypos])
86+
if err != nil {
87+
return position{}, fmt.Errorf("parse y to int: %w", err)
88+
}
89+
90+
return position{
91+
x: x,
92+
y: y,
93+
}, nil
94+
}
95+
96+
func getLines(input io.Reader) ([]line, error) {
97+
scanner := bufio.NewScanner(input)
98+
99+
var lines []line
100+
101+
const (
102+
startpos = 0
103+
endpos = 1
104+
)
105+
106+
for scanner.Scan() {
107+
l := scanner.Text()
108+
109+
coordinates, err := parseLine(l)
110+
if err != nil {
111+
return nil, fmt.Errorf("get numbers: %w", err)
112+
}
113+
114+
lines = append(lines, line{
115+
start: coordinates[startpos],
116+
end: coordinates[endpos],
117+
})
118+
}
119+
120+
if err := scanner.Err(); err != nil {
121+
return nil, fmt.Errorf("scanner error: %w", err)
122+
}
123+
124+
return lines, nil
125+
}
126+
127+
func parseLine(line string) ([]position, error) {
128+
const (
129+
matchNum = 2
130+
)
131+
132+
res := make([]position, 0, matchNum)
133+
134+
matches := reg.FindAllString(line, -1)
135+
if len(matches) != matchNum {
136+
return nil, errors.New("wrong coordinates line")
137+
}
138+
139+
for i := range matches {
140+
coordinates, err := parseCoordinates(matches[i])
141+
if err != nil {
142+
return nil, fmt.Errorf("parse coordinates: %w", err)
143+
}
144+
145+
res = append(res, coordinates)
146+
}
147+
148+
return res, nil
149+
}
150+
151+
type line struct {
152+
start position
153+
end position
154+
}
155+
156+
func (l line) isHorizontal() bool {
157+
return l.start.y == l.end.y
158+
}
159+
160+
func (l line) isVertical() bool {
161+
return l.start.x == l.end.x
162+
}
163+
164+
func (l line) isDiagonal() bool {
165+
return math.Abs(float64(l.start.x-l.end.x)) == math.Abs(float64(l.start.y-l.end.y))
166+
}
167+
168+
type filterFunc func(l line) bool
169+
170+
func part1Filter(l line) bool {
171+
return l.isHorizontal() || l.isVertical()
172+
}
173+
174+
func part2Filter(l line) bool {
175+
return part1Filter(l) || l.isDiagonal()
176+
}
177+
178+
func filterLines(lines []line, filter filterFunc) []line {
179+
filtered := lines[:0]
180+
181+
for _, x := range lines {
182+
if filter(x) {
183+
filtered = append(filtered, x)
184+
}
185+
}
186+
187+
return filtered
188+
}
189+
190+
type dangerFunc func(n int) bool
191+
192+
func isDangerZone(n int) bool {
193+
return n > 1
194+
}
195+
196+
type diagram struct {
197+
data [][]int
198+
}
199+
200+
func (d diagram) dangerZones(f dangerFunc) int {
201+
var zones int
202+
203+
for _, xs := range d.data {
204+
for _, x := range xs {
205+
if f(x) {
206+
zones++
207+
}
208+
}
209+
}
210+
211+
return zones
212+
}
213+
214+
func (d diagram) String() string {
215+
const (
216+
empty = "."
217+
newline = "\n"
218+
)
219+
220+
var res string
221+
222+
last := len(d.data) - 1
223+
224+
for i := 0; i <= last; i++ {
225+
xs := d.data[i]
226+
227+
for _, x := range xs {
228+
if x == 0 {
229+
res += empty
230+
231+
continue
232+
}
233+
234+
res += strconv.Itoa(x)
235+
}
236+
237+
if i != last {
238+
res += newline
239+
}
240+
}
241+
242+
return res
243+
}
244+
245+
func (d *diagram) draw(lines []line) {
246+
for _, l := range lines {
247+
if l.isVertical() {
248+
d.drawVertical(l)
249+
}
250+
251+
if l.isHorizontal() {
252+
d.drawHorizontal(l)
253+
}
254+
255+
if l.isDiagonal() {
256+
d.drawDiagonal(l)
257+
}
258+
}
259+
}
260+
261+
func (d *diagram) drawHorizontal(l line) {
262+
y := l.start.y
263+
264+
x1, x2 := l.start.x, l.end.x
265+
if x1 > x2 {
266+
x1, x2 = x2, x1
267+
}
268+
269+
for i := x1; i <= x2; i++ {
270+
d.data[y][i]++
271+
}
272+
}
273+
274+
func (d *diagram) drawVertical(l line) {
275+
x := l.start.x
276+
277+
y1, y2 := l.start.y, l.end.y
278+
if y1 > y2 {
279+
y1, y2 = y2, y1
280+
}
281+
282+
for i := y1; i <= y2; i++ {
283+
d.data[i][x]++
284+
}
285+
}
286+
287+
func (d *diagram) drawDiagonal(l line) {
288+
x1 := l.start.x
289+
y1 := l.start.y
290+
291+
x2 := l.end.x
292+
y2 := l.end.y
293+
294+
diffX := x1 - x2
295+
diffY := y1 - y2
296+
297+
incrX := -1
298+
if diffX < 0 {
299+
incrX *= -1
300+
}
301+
302+
incrY := -1
303+
if diffY < 0 {
304+
incrY *= -1
305+
}
306+
307+
i, j := x1, y1
308+
309+
for {
310+
d.data[j][i]++
311+
312+
if math.Abs(float64(i-x2)) == 0 && math.Abs(float64(j-y2)) == 0 {
313+
break
314+
}
315+
316+
i, j = i+incrX, j+incrY
317+
}
318+
}
319+
320+
func newDiagram(maxX, maxY int) diagram {
321+
res := make([][]int, maxY+1)
322+
323+
for i := 0; i < maxY+1; i++ {
324+
res[i] = make([]int, maxX+1)
325+
}
326+
327+
return diagram{
328+
data: res,
329+
}
330+
}
331+
332+
func drawDiagram(lines []line) diagram {
333+
// get max x,y
334+
bounds := getBounds(lines)
335+
336+
// allocate
337+
d := newDiagram(bounds.x, bounds.y)
338+
339+
// draw
340+
d.draw(lines)
341+
342+
return d
343+
}
344+
345+
func getBounds(lines []line) position {
346+
var (
347+
maxX, maxY int
348+
)
349+
350+
for _, l := range lines {
351+
if l.start.x > maxX {
352+
maxX = l.start.x
353+
}
354+
355+
if l.start.y > maxY {
356+
maxY = l.start.y
357+
}
358+
359+
if l.end.x > maxX {
360+
maxX = l.end.x
361+
}
362+
363+
if l.end.y > maxY {
364+
maxY = l.end.y
365+
}
366+
}
367+
368+
return position{
369+
x: maxX,
370+
y: maxY,
371+
}
372+
}

0 commit comments

Comments
 (0)