From 5039d8f6a80d7ad5c960ca31c2a3c32c6efee194 Mon Sep 17 00:00:00 2001 From: Gabriel Bizdoc Date: Mon, 8 Dec 2025 19:00:40 +0200 Subject: [PATCH] day 8 --- year25/day8.go | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 year25/day8.go diff --git a/year25/day8.go b/year25/day8.go new file mode 100644 index 0000000..e139e3d --- /dev/null +++ b/year25/day8.go @@ -0,0 +1,185 @@ +package year25 + +import ( + "cmp" + "fmt" + "log" + "math" + "os" + "slices" + "strconv" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" + "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" +) + +type ClusterId int + +type ClustersDB[T any] struct { // TODO: move to devkit + elementIdToCluster []ClusterId + clusterToSize []int +} + +func (s *ClustersDB[T]) GetClusterId(elementId int) ClusterId { + return s.elementIdToCluster[elementId] +} + +func (s *ClustersDB[T]) Move(elementId int, to ClusterId) { + elementCluster := s.GetClusterId(elementId) + + if elementCluster != to { + s.clusterToSize[elementCluster] -= 1 + s.clusterToSize[to] += 1 + + if s.clusterToSize[elementCluster] < 0 { + panic("invalid cluster to size") + } + + s.elementIdToCluster[elementId] = to + } +} + +func (s *ClustersDB[T]) JoinClustersByElements(a, b int) { + elementCluster1 := s.GetClusterId(a) + elementCluster2 := s.GetClusterId(b) + + for el, clusterId := range s.elementIdToCluster { + if clusterId == elementCluster2 { + s.Move(el, elementCluster1) + } + } +} + +func NewClustersDB[T any](data []T) *ClustersDB[T] { + points := make([]ClusterId, len(data)) + sizes := make([]int, len(data)) + + for i := range points { + points[i] = ClusterId(i) + sizes[i] = 1 + } + + return &ClustersDB[T]{ + elementIdToCluster: points, + clusterToSize: sizes, + } +} + +func Day8Part1(ctx aoc.Context, steps int) (int, error) { + ctx.Logger = log.New(os.Stdout, "", 0) + boxes, err := day8ParseInput(ctx) + if err != nil { + return 0, fmt.Errorf("day8: failed to parse input %w", err) + } + + sm := NewClustersDB(boxes) + var distances = SortedDistances(boxes) + + for _, d := range distances[:steps] { + el1Id, el2Id, distance := d.E1, d.E2, d.Distance + if distance != 0 && (sm.GetClusterId(el1Id) != sm.GetClusterId(el2Id)) { + sm.JoinClustersByElements(el1Id, el2Id) + } + } + + ints := slices.Clone(sm.clusterToSize) + slices.Sort(ints) + + sol := 1 + for _, i := range ints[len(ints)-3:] { + sol *= i + } + return sol, nil +} + +func Day8Part2(ctx aoc.Context) (int, error) { + ctx.Logger = log.New(os.Stdout, "", 0) + boxes, err := day8ParseInput(ctx) + if err != nil { + return 0, fmt.Errorf("day8: failed to parse input %w", err) + } + + sm := NewClustersDB(boxes) + var distances = SortedDistances(boxes) + + for _, d := range distances { + el1Id, el2Id, distance := d.E1, d.E2, d.Distance + if distance != 0 && (sm.GetClusterId(el1Id) != sm.GetClusterId(el2Id)) { + sm.JoinClustersByElements(el1Id, el2Id) + if sm.clusterToSize[sm.GetClusterId(el1Id)] == len(boxes) { + return int(boxes[el1Id][0]) * int(boxes[el2Id][0]), nil + } + } + } + + // at this point linked all points + panic("unreachable") +} + +func day8ParseInput(c aoc.Context) ([]Vector, error) { + g, err := geometry.ReaderToIntsGrid(c.Body, ",") + if err != nil { + return nil, fmt.Errorf("day8: failed to read input %w", err) + } + + points := make([]Vector, g.Rows()) + for i := range points { + points[i] = make(Vector, g.Cols()) + for j := range points[i] { + v := g.At(geometry.NewPoint(i, j)) + points[i][j] = float64(v) + } + } + return points, nil +} + +type Vector []float64 // TODO: move to devkit + +func (v Vector) String() string { + x := make([]string, len(v)) + for i, v := range v { + x[i] = strconv.FormatFloat(v, 'f', -1, 64) + } + return fmt.Sprintf("[%g,%g,%g]", v[0], v[1], v[2]) +} + +func SquaredDelta(b, a Vector, dim int) float64 { + x := b[dim] - a[dim] + return x * x +} +func SquaredEuclideanDistance(a, b Vector) (s float64) { + for i := range a { + s += SquaredDelta(a, b, i) + } + return +} +func EuclideanDistance(a, b Vector) float64 { + return math.Sqrt(SquaredEuclideanDistance(a, b)) +} + +type Pair struct { + E1, E2 int + Distance float64 +} + +func SortedDistances(points []Vector) []Pair { + n := len(points) + pairs := make([]Pair, 0, n*(n-1)/2) + + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + pairs = append(pairs, Pair{ + E1: i, + E2: j, + // no need to spend time on sqrt. order does not change + Distance: SquaredEuclideanDistance(points[i], points[j]), + }) + } + } + + slices.SortFunc(pairs, func(a, b Pair) int { + return cmp.Compare(a.Distance, b.Distance) + }) + + return pairs +}