feat: completed day 9 for advent of code 2025
This commit is contained in:
parent
c6651d045a
commit
b64325a60a
4 changed files with 286 additions and 0 deletions
|
|
@ -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
242
2025/day09/day09.go
Executable 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
34
2025/day09/day09_test.go
Executable 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
8
2025/day09/test.txt
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
7,1
|
||||
11,1
|
||||
11,7
|
||||
9,7
|
||||
9,5
|
||||
2,5
|
||||
2,3
|
||||
7,3
|
||||
Loading…
Add table
Add a link
Reference in a new issue