Skip to content

Commit 77ba662

Browse files
committed
2025 day09
1 parent 4dd97a9 commit 77ba662

File tree

6 files changed

+207
-0
lines changed

6 files changed

+207
-0
lines changed

2025/day09/.bench

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Lines":[{"Name":"Part 1","N":5118,"NsPerOp":236842,"AllocedBytesPerOp":0,"AllocsPerOp":0,"MBPerS":0,"Measured":1,"Ord":0},{"Name":"Part 2","N":34,"NsPerOp":33957744,"AllocedBytesPerOp":0,"AllocsPerOp":0,"MBPerS":0,"Measured":1,"Ord":0}],"Measured":1}

2025/day09/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!-- You can add some comments here if you want to :D -->

2025/day09/main.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"image"
7+
"os"
8+
"strings"
9+
)
10+
11+
//go:embed input.txt
12+
var input string
13+
14+
//go:embed input_test.txt
15+
var inputTest string
16+
17+
func main() {
18+
// Check argv if we use test input or not
19+
if len(os.Args) > 1 && os.Args[1] == "test" {
20+
input = inputTest
21+
}
22+
23+
answer := doPartOne(input)
24+
println(answer)
25+
26+
answer = doPartTwo(input)
27+
println(answer)
28+
}
29+
30+
func parseLines(input string) []image.Point {
31+
res := make([]image.Point, 0, len(input))
32+
for _, line := range strings.Split(strings.TrimSpace(input), "\n") {
33+
var c image.Point
34+
fmt.Sscanf(line, "%d,%d", &c.X, &c.Y)
35+
36+
res = append(res, c)
37+
}
38+
return res
39+
}

2025/day09/main_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import "testing"
4+
5+
func BenchmarkPartOne(b *testing.B) {
6+
for n := 0; n < b.N; n++ {
7+
doPartOne(input)
8+
}
9+
}
10+
11+
func BenchmarkPartTwo(b *testing.B) {
12+
for n := 0; n < b.N; n++ {
13+
doPartTwo(input)
14+
}
15+
}

2025/day09/part1.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"aocli/utils"
5+
)
6+
7+
func doPartOne(input string) int {
8+
coords := parseLines(input)
9+
ans := 0
10+
for i, c := range coords {
11+
for _, t := range coords[i+1:] {
12+
w := utils.Abs(t.X-c.X) + 1
13+
h := utils.Abs(t.Y-c.Y) + 1
14+
a := w * h
15+
// fmt.Println(c, t, a)
16+
ans = utils.Ter(ans > a, ans, a)
17+
}
18+
}
19+
return ans
20+
}

2025/day09/part2.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package main
2+
3+
import (
4+
"aocli/utils/polyfence"
5+
"image"
6+
"slices"
7+
)
8+
9+
func doPartTwo(input string) int {
10+
coords := parseLines(input)
11+
pf := polyfence.NewPolyfence(coords)
12+
13+
// Get unique sorted rows and columns for coordinate compression
14+
rowSet := make(map[int]bool)
15+
colSet := make(map[int]bool)
16+
for _, c := range coords {
17+
rowSet[c.Y] = true
18+
colSet[c.X] = true
19+
}
20+
21+
rows := make([]int, 0, len(rowSet))
22+
for r := range rowSet {
23+
rows = append(rows, r)
24+
}
25+
slices.Sort(rows)
26+
27+
cols := make([]int, 0, len(colSet))
28+
for c := range colSet {
29+
cols = append(cols, c)
30+
}
31+
slices.Sort(cols)
32+
33+
// Create index maps
34+
rowIdx := make(map[int]int)
35+
for i, r := range rows {
36+
rowIdx[r] = i
37+
}
38+
colIdx := make(map[int]int)
39+
for i, c := range cols {
40+
colIdx[c] = i
41+
}
42+
43+
// Build compressed grid - mark all points inside polygon
44+
grid := make([][]bool, len(rows))
45+
for i := range grid {
46+
grid[i] = make([]bool, len(cols))
47+
}
48+
49+
for r := range rows {
50+
for c := range cols {
51+
if pf.Inside(image.Point{X: cols[c], Y: rows[r]}) {
52+
grid[r][c] = true
53+
}
54+
}
55+
}
56+
57+
// Build 2D prefix sum
58+
prefixSum := make([][]int, len(rows)+1)
59+
for i := range prefixSum {
60+
prefixSum[i] = make([]int, len(cols)+1)
61+
}
62+
63+
for r := 1; r <= len(rows); r++ {
64+
for c := 1; c <= len(cols); c++ {
65+
val := 0
66+
if grid[r-1][c-1] {
67+
val = 1
68+
}
69+
prefixSum[r][c] = val + prefixSum[r-1][c] + prefixSum[r][c-1] - prefixSum[r-1][c-1]
70+
}
71+
}
72+
73+
// Find max rectangle
74+
maxArea := 0
75+
for i := 0; i < len(coords); i++ {
76+
c1 := coords[i]
77+
r1 := rowIdx[c1.Y] + 1
78+
col1 := colIdx[c1.X] + 1
79+
80+
for j := i + 1; j < len(coords); j++ {
81+
c2 := coords[j]
82+
83+
// Skip lines
84+
if c1.X == c2.X || c1.Y == c2.Y {
85+
continue
86+
}
87+
88+
// Get compressed indices for c2
89+
r2 := rowIdx[c2.Y] + 1
90+
col2 := colIdx[c2.X] + 1
91+
92+
// Calculate min/max
93+
var minR, maxR, minC, maxC int
94+
if r1 < r2 {
95+
minR, maxR = r1, r2
96+
} else {
97+
minR, maxR = r2, r1
98+
}
99+
if col1 < col2 {
100+
minC, maxC = col1, col2
101+
} else {
102+
minC, maxC = col2, col1
103+
}
104+
105+
// Calculate compressed area (number of grid cells)
106+
compressedArea := (maxR - minR + 1) * (maxC - minC + 1)
107+
108+
// Check if all points in rectangle are valid using prefix sum
109+
sum := prefixSum[maxR][maxC] - prefixSum[minR-1][maxC] - prefixSum[maxR][minC-1] + prefixSum[minR-1][minC-1]
110+
111+
if sum == compressedArea {
112+
// Calculate actual area
113+
dx := c2.X - c1.X
114+
dy := c2.Y - c1.Y
115+
if dx < 0 {
116+
dx = -dx
117+
}
118+
if dy < 0 {
119+
dy = -dy
120+
}
121+
area := (dx + 1) * (dy + 1)
122+
123+
if area > maxArea {
124+
maxArea = area
125+
}
126+
}
127+
}
128+
}
129+
130+
return maxArea
131+
}

0 commit comments

Comments
 (0)