feat: solve day 8 part 1...twice

This commit is contained in:
Stevan Freeborn
2025-12-16 13:19:57 -06:00
parent d9be56da3e
commit 9c0f883bc5
11 changed files with 652 additions and 1 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ go test ./cmd/01
| 05 | [Problem](https://adventofcode.com/2025/day/5) | [Solution](./cmd/05/) | Part 1 was fairly straight forward. Merging ranges was trickiest bit. I feel like I'm being led into a trap with how straight forward today was. |
| 06 | [Problem](https://adventofcode.com/2025/day/6) | [Solution](./cmd/06/) | Part 1 I don't think it is the best solution, but it does solve it. I kicked ass on part 2. 🥳 |
| 07 | [Problem](https://adventofcode.com/2025/day/7) | [Solution](./cmd/07/) | FIFO for life. 🤣. Fuck me...part 2 I found really difficult and the use of DFS was obvious, but doing the memoizing was not. |
| 08 | [Problem](https://adventofcode.com/2025/day/8) | [Solution](./cmd/08/) | |
| 08 | [Problem](https://adventofcode.com/2025/day/8) | [Solution](./cmd/08/) | Part 1 had me down bad. I was on the right track with my original solution, but ended up doing it in a brute force way then figuring out my mistake with the original. |
| 09 | [Problem](https://adventofcode.com/2025/day/9) | [Solution](./cmd/09/) | |
| 10 | [Problem](https://adventofcode.com/2025/day/10) | [Solution](./cmd/10/) | |
| 11 | [Problem](https://adventofcode.com/2025/day/11) | [Solution](./cmd/11/) | |
+59
View File
@@ -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,
}
}
+46
View File
@@ -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)
}
}
}
+92
View File
@@ -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
}
+101
View File
@@ -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)
}
}
}
+36
View File
@@ -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),
}
}
+28
View File
@@ -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)
}
}
+57
View File
@@ -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
}
+35
View File
@@ -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
View File
@@ -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
}
+47
View File
@@ -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)
}
}