Skip to content

Commit a3f28eb

Browse files
committed
feat(2024): add day 14
1 parent 02ed5c6 commit a3f28eb

File tree

5 files changed

+246
-0
lines changed

5 files changed

+246
-0
lines changed

go/2024/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Collect stars by solving puzzles. Two puzzles will be made available on each day
2727
| [Day 11: Plutonian Pebbles](https://github.com/believer/advent-of-code/blob/master/go/2024/puzzles/day11/main.go) | 🌟 | 187738 | 🌟 | 223767210249237 |
2828
| [Day 12: Garden Groups](https://github.com/believer/advent-of-code/blob/master/go/2024/puzzles/day12/main.go) | 🌟 | 1522850 | 🌟 | 953738 |
2929
| [Day 13: Claw Contraption](https://github.com/believer/advent-of-code/blob/master/go/2024/puzzles/day13/main.go) | 🌟 | 26299 | 🌟 | 107824497933339 |
30+
| [Day 14: Restroom Redoubt](https://github.com/believer/advent-of-code/blob/master/go/2024/puzzles/day14/main.go) | 🌟 | 228690000 | 🌟 | 7093 |
3031

3132
## Benchmarks
3233

@@ -47,6 +48,7 @@ Using Go's built-in benchmarking with the [testing](https://pkg.go.dev/testing#h
4748
| 11 | 424021 ns/op | 15488584 ns/op | |
4849
| 12 | 6677348 ns/op | 12339733 ns/op | `39.21%` / `26.80%` |
4950
| 13 | 698173 ns/op | 702380 ns/op | `75.93%` / - |
51+
| 14 | 594981 ns/op | 56488050 ns/op | |
5052

5153
\* compared to first solution
5254

go/2024/puzzles/day14/main.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/believer/aoc-2024/utils"
8+
"github.com/believer/aoc-2024/utils/files"
9+
"github.com/believer/aoc-2024/utils/grid"
10+
)
11+
12+
func main() {
13+
fmt.Println("Part 1: ", part1("input.txt", 101, 103))
14+
fmt.Println("Part 2: ", part2("input.txt", 101, 103))
15+
}
16+
17+
type Robot struct {
18+
position grid.Point
19+
velocity grid.Point
20+
}
21+
22+
func part1(name string, width, height int) int {
23+
lines := files.ReadLines(name)
24+
tiles := grid.FromSize(width, height)
25+
robots := parseRobots(lines)
26+
safetyFactor := 1
27+
28+
// Move robots
29+
for range 100 {
30+
for i, robot := range robots {
31+
next := robot.position.Add(robot.velocity)
32+
33+
// Increment or set number of guard in next location
34+
if v := tiles.GetWithWrap(next); v >= '0' && v <= '9' {
35+
tiles.UpdateWithWrap(next, v+1)
36+
} else {
37+
tiles.UpdateWithWrap(next, '1')
38+
}
39+
40+
// Decrement or set old position to empty
41+
if v := tiles.GetWithWrap(robot.position); v > '1' && v <= '9' {
42+
tiles.UpdateWithWrap(robot.position, v-1)
43+
} else {
44+
tiles.UpdateWithWrap(robot.position, '.')
45+
}
46+
47+
robots[i].position = next
48+
}
49+
}
50+
51+
// Get data from quadrants
52+
for _, q := range tiles.GetQuadrants() {
53+
robots := 0
54+
55+
for _, b := range q {
56+
if b == '.' {
57+
continue
58+
}
59+
60+
robots += int(b - '0')
61+
}
62+
63+
safetyFactor *= robots
64+
}
65+
66+
return safetyFactor
67+
}
68+
69+
func part2(name string, width, height int) int {
70+
lines := files.ReadLines(name)
71+
tiles := grid.FromSize(width, height)
72+
robots := parseRobots(lines)
73+
seconds := 0
74+
75+
// Move robots
76+
for s := range 10000 {
77+
for i, robot := range robots {
78+
next := robot.position.Add(robot.velocity)
79+
80+
// Increment or set number of guard in next location
81+
if v := tiles.GetWithWrap(next); v >= '0' && v <= '9' {
82+
tiles.UpdateWithWrap(next, v+1)
83+
} else {
84+
tiles.UpdateWithWrap(next, '1')
85+
}
86+
87+
// Decrement or set old position to empty
88+
if v := tiles.GetWithWrap(robot.position); v > '1' && v <= '9' {
89+
tiles.UpdateWithWrap(robot.position, v-1)
90+
} else {
91+
tiles.UpdateWithWrap(robot.position, '.')
92+
}
93+
94+
robots[i].position = next
95+
}
96+
97+
// Assume that for a Christmas tree to appear there are no overlapping guards
98+
overlaps := true
99+
100+
for _, d := range tiles.Data {
101+
if d != '1' && d != '.' {
102+
overlaps = false
103+
}
104+
}
105+
106+
if overlaps {
107+
// Zero based indexing
108+
seconds = s + 1
109+
break
110+
}
111+
}
112+
113+
return seconds
114+
}
115+
116+
func parseRobots(lines []string) []Robot {
117+
robots := []Robot{}
118+
119+
// Parse out robots
120+
for _, line := range lines {
121+
position, velocity, _ := strings.Cut(line, " ")
122+
x, y, _ := strings.Cut(position[2:], ",")
123+
vx, vy, _ := strings.Cut(velocity[2:], ",")
124+
125+
robots = append(robots, Robot{
126+
position: grid.Point{X: utils.MustIntFromString(x), Y: utils.MustIntFromString(y)},
127+
velocity: grid.Point{X: utils.MustIntFromString(vx), Y: utils.MustIntFromString(vy)},
128+
})
129+
}
130+
131+
return robots
132+
}

go/2024/puzzles/day14/main_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestPart1(t *testing.T) {
10+
t.Run("Part 1", func(t *testing.T) {
11+
expected := 12
12+
actual := part1("test-input.txt", 11, 7)
13+
assert.Equal(t, expected, actual)
14+
})
15+
}
16+
17+
// No test for part 2 since there is no way of getting
18+
// a Christmas tree for so few guards.
19+
20+
func BenchmarkPart1(b *testing.B) {
21+
for i := 0; i < b.N; i++ {
22+
part1("input.txt", 11, 7)
23+
}
24+
}
25+
26+
func BenchmarkPart2(b *testing.B) {
27+
for i := 0; i < b.N; i++ {
28+
part2("input.txt", 11, 7)
29+
}
30+
}

go/2024/puzzles/day14/test-input.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
p=0,4 v=3,-3
2+
p=6,3 v=-1,-3
3+
p=10,3 v=-1,2
4+
p=2,0 v=2,-1
5+
p=0,0 v=1,3
6+
p=3,0 v=-2,-2
7+
p=7,6 v=-1,-3
8+
p=3,0 v=-1,-2
9+
p=9,3 v=2,3
10+
p=7,3 v=-1,2
11+
p=2,4 v=2,-3
12+
p=9,5 v=-3,-3

go/2024/utils/grid/grid.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,33 @@ func New(lines []string) Grid {
6262
}
6363
}
6464

65+
func FromSize(width, height int) Grid {
66+
data := make([]byte, width*height)
67+
68+
for i := range data {
69+
data[i] = '.'
70+
}
71+
72+
return Grid{
73+
Data: data,
74+
Height: height,
75+
Width: width,
76+
}
77+
}
78+
6579
func (g *Grid) Get(p Point) byte {
6680
return g.Data[g.Width*p.Y+p.X]
6781
}
6882

83+
func (g *Grid) GetWithWrap(p Point) byte {
84+
// Wrap coordinates to ensure they stay within bounds
85+
wrappedX := (p.X%g.Width + g.Width) % g.Width
86+
wrappedY := (p.Y%g.Height + g.Height) % g.Height
87+
88+
// Update the grid at the wrapped position
89+
return g.Data[g.Width*wrappedY+wrappedX]
90+
}
91+
6992
func (g *Grid) Contains(p Point) (byte, bool) {
7093
if p.X >= 0 && p.X < g.Width && p.Y >= 0 && p.Y < g.Height {
7194
return g.Get(p), true
@@ -78,6 +101,15 @@ func (g *Grid) Update(p Point, b byte) {
78101
g.Data[g.Width*p.Y+p.X] = b
79102
}
80103

104+
func (g *Grid) UpdateWithWrap(p Point, b byte) {
105+
// Wrap coordinates to ensure they stay within bounds
106+
wrappedX := (p.X%g.Width + g.Width) % g.Width
107+
wrappedY := (p.Y%g.Height + g.Height) % g.Height
108+
109+
// Update the grid at the wrapped position
110+
g.Data[g.Width*wrappedY+wrappedX] = b
111+
}
112+
81113
// Find exactly _one_ point for the given value
82114
func (g *Grid) Find(needle byte) Point {
83115
for i, v := range g.Data {
@@ -108,11 +140,49 @@ func (g *Grid) FindAll(needle byte) []Point {
108140
return points
109141
}
110142

143+
func (g *Grid) GetQuadrant(q int) []byte {
144+
var startX, startY, endX, endY int
145+
146+
halfWidth := g.Width / 2
147+
halfHeight := g.Height / 2
148+
149+
switch q {
150+
case 1: // Top-Left
151+
startX, startY, endX, endY = 0, 0, halfWidth, halfHeight
152+
case 2: // Top-Right
153+
startX, startY, endX, endY = halfWidth+1, 0, g.Width, halfHeight
154+
case 3: // Bottom-Left
155+
startX, startY, endX, endY = 0, halfHeight+1, halfWidth, g.Height
156+
case 4: // Bottom-Right
157+
startX, startY, endX, endY = halfWidth+1, halfHeight+1, g.Width, g.Height
158+
default:
159+
panic("Invalid quadrant number")
160+
}
161+
162+
quadrantData := make([]byte, 0, (endX-startX)*(endY-startY))
163+
for y := startY; y < endY; y++ {
164+
quadrantData = append(quadrantData, g.Data[y*g.Width+startX:y*g.Width+endX]...)
165+
}
166+
167+
return quadrantData
168+
}
169+
170+
func (g *Grid) GetQuadrants() [][]byte {
171+
return [][]byte{
172+
g.GetQuadrant(1),
173+
g.GetQuadrant(2),
174+
g.GetQuadrant(3),
175+
g.GetQuadrant(4),
176+
}
177+
}
178+
111179
func (g *Grid) Debug() {
112180
for y := range g.Height {
113181
for x := range g.Width {
114182
fmt.Print(string(g.Get(Point{X: x, Y: y})))
115183
}
116184
fmt.Println()
117185
}
186+
187+
fmt.Println()
118188
}

0 commit comments

Comments
 (0)