feat: solve day 8 part 1...twice
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// Package box provides a model and methods for junction boxes.
|
||||
package box
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Box interface {
|
||||
Y() int
|
||||
X() int
|
||||
Z() int
|
||||
DistanceFrom(neighbor Box) float64
|
||||
}
|
||||
|
||||
type box struct {
|
||||
x int
|
||||
y int
|
||||
z int
|
||||
}
|
||||
|
||||
func (b box) X() int {
|
||||
return b.x
|
||||
}
|
||||
|
||||
func (b box) Y() int {
|
||||
return b.y
|
||||
}
|
||||
|
||||
func (b box) Z() int {
|
||||
return b.z
|
||||
}
|
||||
|
||||
func (b box) DistanceFrom(neighbor Box) float64 {
|
||||
xDiff := math.Pow(float64(b.x-neighbor.X()), 2)
|
||||
yDiff := math.Pow(float64(b.y-neighbor.Y()), 2)
|
||||
zDiff := math.Pow(float64(b.z-neighbor.Z()), 2)
|
||||
|
||||
return math.Sqrt(xDiff + yDiff + zDiff)
|
||||
}
|
||||
|
||||
func From(str string) Box {
|
||||
parts := strings.Split(str, ",")
|
||||
|
||||
if len(parts) != 3 {
|
||||
panic("invalid junction box")
|
||||
}
|
||||
|
||||
x, _ := strconv.Atoi(parts[0])
|
||||
y, _ := strconv.Atoi(parts[1])
|
||||
z, _ := strconv.Atoi(parts[2])
|
||||
|
||||
return box{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package box_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
)
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected box.Box
|
||||
}{
|
||||
{"1,2,3", box.From("1,2,3")},
|
||||
{"0,0,0", box.From("0,0,0")},
|
||||
{"-1,-2,-3", box.From("-1,-2,-3")},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := box.From(test.input)
|
||||
|
||||
if result != test.expected {
|
||||
t.Errorf("From(%q) = %v; want %v", test.input, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistanceFrom(t *testing.T) {
|
||||
tests := []struct {
|
||||
box1 box.Box
|
||||
box2 box.Box
|
||||
expected float64
|
||||
}{
|
||||
{box.From("0,0,0"), box.From("1,1,1"), 1.7320508075688772},
|
||||
{box.From("1,2,3"), box.From("4,5,6"), 5.196152422706632},
|
||||
{box.From("-1,-2,-3"), box.From("1,2,3"), 7.483314773547883},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := test.box1.DistanceFrom(test.box2)
|
||||
|
||||
if result != test.expected {
|
||||
t.Errorf("DistanceFrom(%v, %v) = %v; want %v", test.box1, test.box2, result, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Package circuitMap provides a data structure to manage and track connected junction boxes.
|
||||
package circuitMap
|
||||
|
||||
import "github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
|
||||
type CircuitMap interface {
|
||||
Length() int
|
||||
GetValueFor(b box.Box) (CircuitMapValue, bool)
|
||||
UpdateValueFor(b box.Box, value CircuitMapValue)
|
||||
FindRootBoxFor(b box.Box) box.Box
|
||||
Entries() map[box.Box]CircuitMapValue
|
||||
}
|
||||
|
||||
type CircuitMapValue interface {
|
||||
Parent() box.Box
|
||||
Size() int
|
||||
UpdateParent(newParent box.Box)
|
||||
IncreaseSize(by int)
|
||||
}
|
||||
|
||||
type circuitMap map[box.Box]CircuitMapValue
|
||||
|
||||
func From(boxes []box.Box) CircuitMap {
|
||||
circuitsMap := circuitMap{}
|
||||
|
||||
for _, b := range boxes {
|
||||
circuitsMap[b] = &circuitMapValue{
|
||||
parent: b,
|
||||
size: 1,
|
||||
}
|
||||
}
|
||||
|
||||
return circuitsMap
|
||||
}
|
||||
|
||||
func (c circuitMap) Length() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c circuitMap) GetValueFor(b box.Box) (CircuitMapValue, bool) {
|
||||
v, exists := c[b]
|
||||
return v, exists
|
||||
}
|
||||
|
||||
func (c circuitMap) UpdateValueFor(b box.Box, value CircuitMapValue) {
|
||||
c[b] = value
|
||||
}
|
||||
|
||||
func (c circuitMap) FindRootBoxFor(b box.Box) box.Box {
|
||||
root := b
|
||||
|
||||
for c[root].Parent() != root {
|
||||
root = c[root].Parent()
|
||||
}
|
||||
|
||||
current := b
|
||||
|
||||
for current != root {
|
||||
item := c[current]
|
||||
next := item.Parent()
|
||||
item.UpdateParent(root)
|
||||
c[current] = item
|
||||
current = next
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
func (c circuitMap) Entries() map[box.Box]CircuitMapValue {
|
||||
return c
|
||||
}
|
||||
|
||||
type circuitMapValue struct {
|
||||
parent box.Box
|
||||
size int
|
||||
}
|
||||
|
||||
func (c *circuitMapValue) Parent() box.Box {
|
||||
return c.parent
|
||||
}
|
||||
|
||||
func (c *circuitMapValue) Size() int {
|
||||
return c.size
|
||||
}
|
||||
|
||||
func (c *circuitMapValue) UpdateParent(newParent box.Box) {
|
||||
c.parent = newParent
|
||||
}
|
||||
|
||||
func (c *circuitMapValue) IncreaseSize(by int) {
|
||||
c.size += by
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package circuitMap_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/circuitMap"
|
||||
)
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
boxes := []box.Box{
|
||||
box.From("0,0,0"),
|
||||
box.From("1,1,1"),
|
||||
}
|
||||
expectedNumOfEntries := len(boxes)
|
||||
|
||||
result := circuitMap.From(boxes)
|
||||
|
||||
if result.Length() != expectedNumOfEntries {
|
||||
t.Errorf("CreateMap() returned map with %d entries; expected %d", result.Length(), expectedNumOfEntries)
|
||||
}
|
||||
|
||||
for _, b := range boxes {
|
||||
_, exists := result.GetValueFor(b)
|
||||
|
||||
if !exists {
|
||||
t.Errorf("CreateMap() result missing entry for box %v", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAndUpdateValueFor(t *testing.T) {
|
||||
b := box.From("2,2,2")
|
||||
cMap := circuitMap.From([]box.Box{b})
|
||||
|
||||
value, exists := cMap.GetValueFor(b)
|
||||
|
||||
if !exists {
|
||||
t.Fatalf("GetValueFor() did not find value for box %v", b)
|
||||
}
|
||||
|
||||
if value.Parent() != b {
|
||||
t.Errorf("GetValueFor() returned value with Parent() = %v; expected %v", value.Parent(), b)
|
||||
}
|
||||
|
||||
newParent := box.From("3,3,3")
|
||||
|
||||
value.UpdateParent(newParent)
|
||||
cMap.UpdateValueFor(b, value)
|
||||
|
||||
updatedValue, exists := cMap.GetValueFor(b)
|
||||
|
||||
if !exists {
|
||||
t.Fatalf("GetValueFor() after UpdateValueFor() did not find value for box %v", b)
|
||||
}
|
||||
|
||||
if updatedValue.Parent() != newParent {
|
||||
t.Errorf("After UpdateValueFor(), GetValueFor() returned value with Parent() = %v; expected %v", updatedValue.Parent(), newParent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindRootBoxFor(t *testing.T) {
|
||||
b1 := box.From("0,0,0")
|
||||
b2 := box.From("1,1,1")
|
||||
b3 := box.From("2,2,2")
|
||||
|
||||
cMap := circuitMap.From([]box.Box{b1, b2, b3})
|
||||
|
||||
// Manually create connections
|
||||
value2, _ := cMap.GetValueFor(b2)
|
||||
value2.UpdateParent(b1)
|
||||
cMap.UpdateValueFor(b2, value2)
|
||||
|
||||
value3, _ := cMap.GetValueFor(b3)
|
||||
value3.UpdateParent(b2)
|
||||
cMap.UpdateValueFor(b3, value3)
|
||||
|
||||
root := cMap.FindRootBoxFor(b3)
|
||||
|
||||
if root != b1 {
|
||||
t.Errorf("FindRootBoxFor(%v) = %v; expected %v", b3, root, b1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntries(t *testing.T) {
|
||||
b1 := box.From("0,0,0")
|
||||
b2 := box.From("1,1,1")
|
||||
|
||||
cMap := circuitMap.From([]box.Box{b1, b2})
|
||||
entries := cMap.Entries()
|
||||
|
||||
if len(entries) != 2 {
|
||||
t.Errorf("Entries() returned %d entries; expected 2", len(entries))
|
||||
}
|
||||
|
||||
for _, b := range []box.Box{b1, b2} {
|
||||
if _, exists := entries[b]; !exists {
|
||||
t.Errorf("Entries() missing entry for box %v", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Package connection provides a model and methods for connections between junction boxes.
|
||||
package connection
|
||||
|
||||
import "github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
|
||||
type Connection interface {
|
||||
Start() box.Box
|
||||
End() box.Box
|
||||
Distance() float64
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
start box.Box
|
||||
end box.Box
|
||||
distance float64
|
||||
}
|
||||
|
||||
func (c connection) Start() box.Box {
|
||||
return c.start
|
||||
}
|
||||
|
||||
func (c connection) End() box.Box {
|
||||
return c.end
|
||||
}
|
||||
|
||||
func (c connection) Distance() float64 {
|
||||
return c.distance
|
||||
}
|
||||
|
||||
func From(start box.Box, end box.Box) Connection {
|
||||
return connection{
|
||||
start: start,
|
||||
end: end,
|
||||
distance: start.DistanceFrom(end),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package connection_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/connection"
|
||||
)
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
start := box.From("1,2,3")
|
||||
end := box.From("4,5,6")
|
||||
expectedDistance := start.DistanceFrom(end)
|
||||
|
||||
conn := connection.From(start, end)
|
||||
|
||||
if conn.Start() != start {
|
||||
t.Errorf("Connection Start() = %v; want %v", conn.Start(), start)
|
||||
}
|
||||
|
||||
if conn.End() != end {
|
||||
t.Errorf("Connection End() = %v; want %v", conn.End(), end)
|
||||
}
|
||||
|
||||
if conn.Distance() != expectedDistance {
|
||||
t.Errorf("Connection Distance() = %v; want %v", conn.Distance(), expectedDistance)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Package estimator provides model and methods
|
||||
// for answering questions about cable needs
|
||||
// for connecting junction boxes.
|
||||
package estimator
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/circuitMap"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/connection"
|
||||
)
|
||||
|
||||
type Estimator interface {
|
||||
PossibleConnections() []connection.Connection
|
||||
CreateMap() circuitMap.CircuitMap
|
||||
}
|
||||
|
||||
type estimator struct {
|
||||
possibleConnections []connection.Connection
|
||||
boxes []box.Box
|
||||
}
|
||||
|
||||
func From(boxes []box.Box) Estimator {
|
||||
return estimator{
|
||||
boxes: boxes,
|
||||
possibleConnections: createAllPossibleConnections(boxes),
|
||||
}
|
||||
}
|
||||
|
||||
func (e estimator) PossibleConnections() []connection.Connection {
|
||||
return e.possibleConnections
|
||||
}
|
||||
|
||||
func (e estimator) CreateMap() circuitMap.CircuitMap {
|
||||
return circuitMap.From(e.boxes)
|
||||
}
|
||||
|
||||
func createAllPossibleConnections(boxes []box.Box) []connection.Connection {
|
||||
connections := []connection.Connection{}
|
||||
numOfBoxes := len(boxes)
|
||||
|
||||
for i := range numOfBoxes {
|
||||
for j := i + 1; j < numOfBoxes; j++ {
|
||||
start := boxes[i]
|
||||
end := boxes[j]
|
||||
conn := connection.From(start, end)
|
||||
connections = append(connections, conn)
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(connections, func(a connection.Connection, b connection.Connection) int {
|
||||
return int(a.Distance() - b.Distance())
|
||||
})
|
||||
|
||||
return connections
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package estimator_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/estimator"
|
||||
)
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
result := estimator.From([]box.Box{})
|
||||
|
||||
if result == nil {
|
||||
t.Errorf("estimator.From returned nil; expected non-nil value")
|
||||
}
|
||||
|
||||
if len(result.PossibleConnections()) != 0 {
|
||||
t.Errorf("estimator.From returned estimator with PossibleConnections() = %d; expected 0", result.PossibleConnections())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPossibleConnections(t *testing.T) {
|
||||
expectedNumOfConnections := 1
|
||||
|
||||
est := estimator.From([]box.Box{
|
||||
box.From("0,0,0"),
|
||||
box.From("1,1,1"),
|
||||
})
|
||||
|
||||
result := est.PossibleConnections()
|
||||
|
||||
if len(result) != expectedNumOfConnections {
|
||||
t.Errorf("PossibleConnections() returned %d connections; expected %d", len(result), expectedNumOfConnections)
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/box"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/cmd/08/estimator"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/internal/file"
|
||||
"github.com/StevanFreeborn/advent-of-code-2025/internal/queue"
|
||||
)
|
||||
|
||||
func SolvePartOne(filePath string, numOfConnections int) int {
|
||||
boxes := []box.Box{}
|
||||
|
||||
for line := range file.ReadLines(filePath) {
|
||||
boxes = append(boxes, box.From(line))
|
||||
}
|
||||
|
||||
estimator := estimator.From(boxes)
|
||||
circuitsMap := estimator.CreateMap()
|
||||
|
||||
for _, connection := range estimator.PossibleConnections()[:numOfConnections] {
|
||||
startRoot := circuitsMap.FindRootBoxFor(connection.Start())
|
||||
endRoot := circuitsMap.FindRootBoxFor(connection.End())
|
||||
|
||||
if startRoot == endRoot {
|
||||
continue
|
||||
}
|
||||
|
||||
startRootValue, _ := circuitsMap.GetValueFor(startRoot)
|
||||
endRootValue, _ := circuitsMap.GetValueFor(endRoot)
|
||||
|
||||
if startRootValue.Size() < endRootValue.Size() {
|
||||
startRootValue.UpdateParent(endRoot)
|
||||
endRootValue.IncreaseSize(startRootValue.Size())
|
||||
} else {
|
||||
endRootValue.UpdateParent(startRoot)
|
||||
startRootValue.IncreaseSize(endRootValue.Size())
|
||||
}
|
||||
|
||||
circuitsMap.UpdateValueFor(startRoot, startRootValue)
|
||||
circuitsMap.UpdateValueFor(endRoot, endRootValue)
|
||||
}
|
||||
|
||||
circuitSizes := []int{}
|
||||
|
||||
for root, circuit := range circuitsMap.Entries() {
|
||||
if circuit.Parent() != root {
|
||||
continue
|
||||
}
|
||||
|
||||
circuitSizes = append(circuitSizes, circuit.Size())
|
||||
}
|
||||
|
||||
slices.SortFunc(circuitSizes, func(a int, b int) int {
|
||||
return b - a
|
||||
})
|
||||
|
||||
if len(circuitSizes) < 3 {
|
||||
panic("not enough circuits")
|
||||
}
|
||||
|
||||
result := 1
|
||||
|
||||
for i := range 3 {
|
||||
result *= circuitSizes[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func SolvePartOneAgain(filePath string, numOfConnections int) int {
|
||||
boxes := []box.Box{}
|
||||
|
||||
for line := range file.ReadLines(filePath) {
|
||||
boxes = append(boxes, box.From(line))
|
||||
}
|
||||
|
||||
estimator := estimator.From(boxes)
|
||||
|
||||
neighbors := map[box.Box][]box.Box{}
|
||||
|
||||
for _, b := range boxes {
|
||||
neighbors[b] = []box.Box{}
|
||||
}
|
||||
|
||||
for _, connection := range estimator.PossibleConnections()[:numOfConnections] {
|
||||
startNeighbors := neighbors[connection.Start()]
|
||||
endNeighbors := neighbors[connection.End()]
|
||||
|
||||
startNeighbors = append(startNeighbors, connection.End())
|
||||
endNeighbors = append(endNeighbors, connection.Start())
|
||||
|
||||
neighbors[connection.Start()] = startNeighbors
|
||||
neighbors[connection.End()] = endNeighbors
|
||||
}
|
||||
|
||||
circuits := [][]box.Box{}
|
||||
visited := map[box.Box]bool{}
|
||||
|
||||
for _, b := range boxes {
|
||||
if visited[b] {
|
||||
continue
|
||||
}
|
||||
|
||||
currentCircuit := []box.Box{}
|
||||
boxesQueue := queue.New[box.Box]()
|
||||
boxesQueue.Enqueue(b)
|
||||
visited[b] = true
|
||||
|
||||
for boxesQueue.IsEmpty() == false {
|
||||
current, _ := boxesQueue.Dequeue()
|
||||
currentCircuit = append(currentCircuit, current)
|
||||
currentNeighbors := neighbors[current]
|
||||
|
||||
for _, n := range currentNeighbors {
|
||||
if visited[n] {
|
||||
continue
|
||||
}
|
||||
|
||||
visited[n] = true
|
||||
boxesQueue.Enqueue(n)
|
||||
}
|
||||
}
|
||||
|
||||
circuits = append(circuits, currentCircuit)
|
||||
}
|
||||
|
||||
circuitSizes := []int{}
|
||||
|
||||
for _, c := range circuits {
|
||||
circuitSizes = append(circuitSizes, len(c))
|
||||
}
|
||||
|
||||
slices.SortFunc(circuitSizes, func(a int, b int) int {
|
||||
return b - a
|
||||
})
|
||||
|
||||
if len(circuitSizes) < 3 {
|
||||
panic("not enough circuits")
|
||||
}
|
||||
|
||||
result := 1
|
||||
|
||||
for i := range 3 {
|
||||
result *= circuitSizes[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
solution "github.com/StevanFreeborn/advent-of-code-2025/cmd/08"
|
||||
)
|
||||
|
||||
func TestSolvePartOneWithExampleInput(t *testing.T) {
|
||||
expected := 40
|
||||
|
||||
result := solution.SolvePartOne("EXAMPLE.txt", 10)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("SolvePartOne returned %d, expected %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolvePartOneWithInput(t *testing.T) {
|
||||
expected := 62186
|
||||
|
||||
result := solution.SolvePartOne("INPUT.txt", 1000)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("SolvePartOne returned %d, expected %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolvePartOneAgainWithExampleInput(t *testing.T) {
|
||||
expected := 40
|
||||
|
||||
result := solution.SolvePartOneAgain("EXAMPLE.txt", 10)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("SolvePartOneAgain returned %d, expected %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolvePartOneAgainWithInput(t *testing.T) {
|
||||
expected := 62186
|
||||
|
||||
result := solution.SolvePartOneAgain("INPUT.txt", 1000)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("SolvePartOneAgain returned %d, expected %d", result, expected)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user