Skip to content

Commit 433c50e

Browse files
committed
refactor(2025): massive performance increase in day 9 part 2
1 parent 7d21864 commit 433c50e

1 file changed

Lines changed: 61 additions & 37 deletions

File tree

go/2025/puzzles/day09/main.go

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
// Then I found the line in the problem that says adjacent coordinates are
1616
// on the same column or row, which made boundary calculations easier.
1717

18+
// Edit: Found a faster solution using slice bounds instead of a map
19+
// TODO:<Perf> Add performance from M1 Mac for old solution and new
20+
1821
func main() {
1922
fmt.Println("Part 1: ", part1("input.txt"))
2023
fmt.Println("Part 2: ", part2("input.txt"))
@@ -53,7 +56,7 @@ type Rectangle struct {
5356
func part2(name string) (largestArea int) {
5457
lines := files.ReadLines(name)
5558
coords := createCoordinates(lines)
56-
boundaries := createBoundaries(coords)
59+
bounds, minY := createBoundariesSlice(coords)
5760

5861
// Create all rectangles first. Might be bad for
5962
// performance, but I like how it looks
@@ -81,7 +84,7 @@ func part2(name string) (largestArea int) {
8184

8285
// Find the first valid
8386
for _, r := range rectangles {
84-
if isRectangleValid(r, boundaries) {
87+
if isRectangleValid(r, bounds, minY) {
8588
largestArea = r.Area
8689
break
8790
}
@@ -104,20 +107,22 @@ func createCoordinates(lines []string) (coords []grid.Point) {
104107
}
105108

106109
// This is slow because of, potentially, a lot of checks
107-
func isRectangleValid(r Rectangle, boundaries map[int][2]int) bool {
110+
func isRectangleValid(r Rectangle, boundaries []Bounds, minY int) bool {
108111
minX := min(r.P1.X, r.P2.X)
109112
maxX := max(r.P1.X, r.P2.X)
110-
minY := min(r.P1.Y, r.P2.Y)
111-
maxY := max(r.P1.Y, r.P2.Y)
113+
loY := min(r.P1.Y, r.P2.Y)
114+
hiY := max(r.P1.Y, r.P2.Y)
112115

113-
for y := minY; y <= maxY; y++ {
114-
bounds, ok := boundaries[y]
116+
for y := loY; y <= hiY; y++ {
117+
index := y - minY
115118

116-
if !ok {
119+
if index < 0 || index >= len(boundaries) {
117120
return false
118121
}
119122

120-
if minX < bounds[0] || maxX > bounds[1] {
123+
b := boundaries[index]
124+
125+
if !b.Ok || minX < b.Min || maxX > b.Max {
121126
return false
122127
}
123128
}
@@ -130,53 +135,72 @@ func isRectangleValid(r Rectangle, boundaries map[int][2]int) bool {
130135
// your list will always be on either the same row or the same column".
131136
// So, we can find vertical and horizontal edges
132137

133-
// row -> [min_x, max_x]
134-
func createBoundaries(coords []grid.Point) map[int][2]int {
135-
boundaries := map[int][2]int{}
138+
type Bounds struct {
139+
Min, Max int
140+
Ok bool
141+
}
142+
143+
// First solution had a map of minX, maxX. But, this was slow when checking
144+
// for valid rectangles. This returns a slice and minY for faster lookup with y-minY.
145+
func createBoundariesSlice(coords []grid.Point) ([]Bounds, int) {
146+
if len(coords) == 0 {
147+
return nil, 0
148+
}
149+
150+
// Find global Y-range
151+
minY := coords[0].Y
152+
maxY := coords[0].Y
153+
154+
for _, c := range coords {
155+
minY = min(c.Y, minY)
156+
maxY = max(c.Y, maxY)
157+
}
136158

137-
for i, c := range coords {
138-
p1 := c
139-
p2 := coords[(i+1)%len(coords)] // List wraps around
159+
// Allocate bounds
160+
size := maxY - minY + 1
161+
bounds := make([]Bounds, size)
162+
163+
// Build similar to your map version
164+
for i := range coords {
165+
p1 := coords[i]
166+
p2 := coords[(i+1)%len(coords)]
140167

141168
x1, y1 := p1.X, p1.Y
142169
x2, y2 := p2.X, p2.Y
143170

144171
if x1 == x2 {
145172
// Same x, create vertical edge from min y to max y
146-
minY := min(y1, y2)
147-
maxY := max(y1, y2) + 1
148-
149-
for y := minY; y < maxY; y++ {
150-
if _, ok := boundaries[y]; !ok {
151-
boundaries[y] = [2]int{x1, x1}
152-
} else {
153-
cx1 := boundaries[y][0]
154-
cx2 := boundaries[y][1]
173+
lo := min(y1, y2)
174+
hi := max(y1, y2)
155175

156-
minX := min(cx1, x1)
157-
maxX := max(cx2, x1)
176+
for y := lo; y <= hi; y++ {
177+
idx := y - minY
158178

159-
boundaries[y] = [2]int{minX, maxX}
179+
if !bounds[idx].Ok {
180+
bounds[idx] = Bounds{Min: x1, Max: x1, Ok: true}
181+
} else {
182+
b := bounds[idx]
183+
b.Min = min(x1, b.Min)
184+
b.Max = max(x1, b.Max)
185+
bounds[idx] = b
160186
}
161187
}
162188
} else if y1 == y2 {
163189
// same y, create horizontal edge from mix to max x
190+
row := y1 - minY
164191

165-
if _, ok := boundaries[y1]; !ok {
166-
boundaries[y1] = [2]int{min(x1, x2), max(x1, x2)}
192+
if !bounds[row].Ok {
193+
bounds[row] = Bounds{Min: min(x1, x2), Max: max(x1, x2), Ok: true}
167194
} else {
168-
cx1 := boundaries[y1][0]
169-
cx2 := boundaries[y1][1]
170-
171-
minX := min(min(cx1, x1), x2)
172-
maxX := max(max(cx2, x1), x2)
173-
174-
boundaries[y1] = [2]int{minX, maxX}
195+
b := bounds[row]
196+
b.Min = min(min(x1, x2), b.Min)
197+
b.Max = max(max(x1, x2), b.Max)
198+
bounds[row] = b
175199
}
176200
}
177201
}
178202

179-
return boundaries
203+
return bounds, minY
180204
}
181205

182206
// math.Min / math.Max use float64s so these helpers

0 commit comments

Comments
 (0)