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