186 lines
4.0 KiB
Go
186 lines
4.0 KiB
Go
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
|
|
}
|