feat: completed day 8 for advent of code 2025

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

View file

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

203
2025/day08/day08.go Executable file
View file

@ -0,0 +1,203 @@
package day08
import (
"bufio"
"fmt"
"math"
"os"
"sort"
)
type Point struct {
x, y, z int
}
type Pair struct {
i, j int
dist float64
}
type UnionFind struct {
parent []int
size []int
}
func newUnionFind(n int) *UnionFind {
uf := &UnionFind{
parent: make([]int, n),
size: make([]int, n),
}
for i := range uf.parent {
uf.parent[i] = i
uf.size[i] = 1
}
return uf
}
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
func (uf *UnionFind) Union(x, y int) {
xRoot := uf.Find(x)
yRoot := uf.Find(y)
if xRoot == yRoot {
return
}
if uf.size[xRoot] < uf.size[yRoot] {
uf.parent[xRoot] = yRoot
uf.size[yRoot] += uf.size[xRoot]
} else {
uf.parent[yRoot] = xRoot
uf.size[xRoot] += uf.size[yRoot]
}
}
func (uf *UnionFind) ComponentSizes() map[int]int {
sizes := make(map[int]int)
for i := range uf.parent {
root := uf.Find(i)
sizes[root]++
}
return sizes
}
type PairHeap []Pair
func (h PairHeap) Len() int { return len(h) }
func (h PairHeap) Less(i, j int) bool {
if h[i].dist != h[j].dist {
return h[i].dist < h[j].dist
}
if h[i].i != h[j].i {
return h[i].i < h[j].i
}
return h[i].j < h[j].j
}
func (h PairHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *PairHeap) Push(x any) { *h = append(*h, x.(Pair)) }
func (h *PairHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func Part1(points []Point, K int) int {
n := len(points)
var pairs []Pair
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
dx := float64(points[i].x - points[j].x)
dy := float64(points[i].y - points[j].y)
dz := float64(points[i].z - points[j].z)
dist := math.Sqrt(dx*dx + dy*dy + dz*dz)
pairs = append(pairs, Pair{i, j, dist})
}
}
sort.Slice(pairs, func(a, b int) bool {
if pairs[a].dist != pairs[b].dist {
return pairs[a].dist < pairs[b].dist
}
if pairs[a].i != pairs[b].i {
return pairs[a].i < pairs[b].i
}
return pairs[a].j < pairs[b].j
})
uf := newUnionFind(n)
for i := 0; i < K && i < len(pairs); i++ {
p := pairs[i]
uf.Union(p.i, p.j)
}
sizes := uf.ComponentSizes()
var sizeList []int
for _, sz := range sizes {
sizeList = append(sizeList, sz)
}
sort.Sort(sort.Reverse(sort.IntSlice(sizeList)))
if len(sizeList) < 3 {
return -1
}
return sizeList[0] * sizeList[1] * sizeList[2]
}
func Part2(points []Point) int {
n := len(points)
var pairs []Pair
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
dx := float64(points[i].x - points[j].x)
dy := float64(points[i].y - points[j].y)
dz := float64(points[i].z - points[j].z)
dist := math.Sqrt(dx*dx + dy*dy + dz*dz)
pairs = append(pairs, Pair{i, j, dist})
}
}
sort.Slice(pairs, func(a, b int) bool {
if pairs[a].dist != pairs[b].dist {
return pairs[a].dist < pairs[b].dist
}
if pairs[a].i != pairs[b].i {
return pairs[a].i < pairs[b].i
}
return pairs[a].j < pairs[b].j
})
uf := newUnionFind(n)
merges := 0
var lastPair Pair
for _, p := range pairs {
if uf.Find(p.i) != uf.Find(p.j) {
uf.Union(p.i, p.j)
merges++
lastPair = p
if merges == n-1 {
break
}
}
}
return points[lastPair.i].x * points[lastPair.j].x
}
func Run(filename string) (any, any) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
var points []Point
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var x, y, z int
_, err := fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z)
if err != nil {
panic("invalid input line: " + line)
}
points = append(points, Point{x, y, z})
}
if err := scanner.Err(); err != nil {
panic(err)
}
K := 1000
part1 := Part1(points, K)
part2 := Part2(points)
return part1, part2
}

34
2025/day08/day08_test.go Executable file
View file

@ -0,0 +1,34 @@
package day08
import (
"os"
"testing"
)
type TestCase struct {
FileName string
Part1 any
Part2 any
}
func TestDay08(t *testing.T) {
tests := []TestCase{
{"test.txt", -1, 25272},
{"input.txt", 164475, 169521198},
}
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)
}
}
}

20
2025/day08/test.txt Executable file
View file

@ -0,0 +1,20 @@
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689