feat: completed day 8 for advent of code 2025
This commit is contained in:
parent
3399702096
commit
c6651d045a
4 changed files with 259 additions and 0 deletions
|
|
@ -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
203
2025/day08/day08.go
Executable 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
34
2025/day08/day08_test.go
Executable 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
20
2025/day08/test.txt
Executable 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue