feat: completed day 9 for advent of code 2025

This commit is contained in:
Ceferino Patino 2025-12-13 13:09:15 -06:00
commit b64325a60a
Signed by: c4patino
SSH key fingerprint: SHA256:Wu+cU1t+7zVT9wzew/4meNmeulo66NzMqc22MdDBgXI
4 changed files with 286 additions and 0 deletions

View file

@ -17,6 +17,7 @@ import (
"cpatino.com/advent-of-code/2025/day06"
"cpatino.com/advent-of-code/2025/day07"
"cpatino.com/advent-of-code/2025/day08"
"cpatino.com/advent-of-code/2025/day09"
)
var days = map[int]func(string) (any, any){
@ -28,6 +29,7 @@ var days = map[int]func(string) (any, any){
6: day06.Run,
7: day07.Run,
8: day08.Run,
9: day09.Run,
}
var rootCmd = &cobra.Command{

242
2025/day09/day09.go Executable file
View file

@ -0,0 +1,242 @@
package day09
import (
"bufio"
"fmt"
"os"
"runtime"
"sync"
)
type Point struct {
x, y int
}
var tiles []Point
func Part1(tiles []Point) int {
n := len(tiles)
if n < 2 {
return 0
}
numWorkers := runtime.NumCPU()
maxChan := make(chan int, numWorkers)
var wg sync.WaitGroup
chunkSize := (n + numWorkers - 1) / numWorkers
for w := range numWorkers {
start := w * chunkSize
end := min(start+chunkSize, n)
wg.Add(1)
go func(start, end int) {
defer wg.Done()
localMax := 0
for i := start; i < end; i++ {
for j := i + 1; j < n; j++ {
t1 := tiles[i]
t2 := tiles[j]
area := (abs(t1.x-t2.x) + 1) * (abs(t1.y-t2.y) + 1)
if area > localMax {
localMax = area
}
}
}
maxChan <- localMax
}(start, end)
}
wg.Wait()
close(maxChan)
maxArea := 0
for v := range maxChan {
if v > maxArea {
maxArea = v
}
}
return maxArea
}
func Part2(tiles []Point) int {
if len(tiles) < 2 {
return 0
}
red := make(map[Point]bool, len(tiles))
for _, p := range tiles {
red[p] = true
}
green := make(map[Point]bool)
n := len(tiles)
for i := range n {
a := tiles[i]
b := tiles[(i+1)%n]
if a.x == b.x {
sy, ey := a.y, b.y
if sy > ey {
sy, ey = ey, sy
}
for y := sy + 1; y < ey; y++ {
pt := Point{a.x, y}
if !red[pt] {
green[pt] = true
}
}
} else if a.y == b.y {
sx, ex := a.x, b.x
if sx > ex {
sx, ex = ex, sx
}
for x := sx + 1; x < ex; x++ {
pt := Point{x, a.y}
if !red[pt] {
green[pt] = true
}
}
}
}
type vedge struct{ x, y1, y2 int }
var vedges []vedge
for i := range n {
a := tiles[i]
b := tiles[(i+1)%n]
if a.x == b.x {
sy, ey := a.y, b.y
if sy > ey {
sy, ey = ey, sy
}
vedges = append(vedges, vedge{a.x, sy, ey})
}
}
isInside := func(p Point) bool {
cross := 0
for _, e := range vedges {
if p.y > e.y1 && p.y <= e.y2 { // edge crosses scanline at p.y
if e.x > p.x {
cross++
}
}
}
return cross%2 == 1
}
if n < 2 {
return 0
}
numWorkers := runtime.NumCPU()
maxChan := make(chan int, numWorkers)
var wg sync.WaitGroup
chunkSize := (n + numWorkers - 1) / numWorkers
for w := range numWorkers {
start := w * chunkSize
end := start + chunkSize
if end > n {
end = n
}
wg.Add(1)
go func(start, end int) {
defer wg.Done()
localMax := 0
for i := start; i < end; i++ {
for j := i + 1; j < n; j++ {
t1, t2 := tiles[i], tiles[j]
sx, ex := t1.x, t2.x
if sx > ex {
sx, ex = ex, sx
}
sy, ey := t1.y, t2.y
if sy > ey {
sy, ey = ey, sy
}
valid := true
for x := sx; x <= ex && valid; x++ {
for _, y := range []int{sy, ey} {
pt := Point{x, y}
if !red[pt] && !green[pt] && !isInside(pt) {
valid = false
break
}
}
}
for y := sy + 1; y < ey && valid; y++ {
for _, x := range []int{sx, ex} {
pt := Point{x, y}
if !red[pt] && !green[pt] && !isInside(pt) {
valid = false
break
}
}
}
if valid {
area := (ex - sx + 1) * (ey - sy + 1)
if area > localMax {
localMax = area
}
}
}
}
maxChan <- localMax
}(start, end)
}
wg.Wait()
close(maxChan)
maxArea := 0
for v := range maxChan {
if v > maxArea {
maxArea = v
}
}
return maxArea
}
func abs(a int) int {
if a < 0 {
return -a
}
return a
}
func Run(filename string) (any, any) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
tiles = nil
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
var x, y int
_, err := fmt.Sscanf(line, "%d,%d", &x, &y)
if err != nil {
panic(fmt.Sprintf("failed to parse line: %q: %v", line, err))
}
tiles = append(tiles, Point{x, y})
}
if err := scanner.Err(); err != nil {
panic(err)
}
part1 := Part1(tiles)
part2 := Part2(tiles)
return part1, part2
}

34
2025/day09/day09_test.go Executable file
View file

@ -0,0 +1,34 @@
package day09
import (
"os"
"testing"
)
type TestCase struct {
FileName string
Part1 any
Part2 any
}
func TestDay09(t *testing.T) {
tests := []TestCase{
{"test.txt", 50, 24},
{"input.txt", 4777824480, 1542119040},
}
for _, test := range tests {
if _, err := os.Stat(test.FileName); os.IsNotExist(err) {
t.Fatalf("test file does not exist: %v", test.FileName)
}
part1, part2 := Run(test.FileName)
if test.Part1 != nil && part1 != test.Part1 {
t.Fatalf("%s: unexpected part1:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part1, part1)
}
if test.Part2 != nil && part2 != test.Part2 {
t.Fatalf("%s: unexpected part2:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part2, part2)
}
}
}

8
2025/day09/test.txt Executable file
View file

@ -0,0 +1,8 @@
7,1
11,1
11,7
9,7
9,5
2,5
2,3
7,3