diff --git a/README.md b/README.md index b81b00d..1ac8d25 100644 --- a/README.md +++ b/README.md @@ -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/) | | diff --git a/cmd/08/box/box.go b/cmd/08/box/box.go new file mode 100644 index 0000000..32d060b --- /dev/null +++ b/cmd/08/box/box.go @@ -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, + } +} diff --git a/cmd/08/box/box_test.go b/cmd/08/box/box_test.go new file mode 100644 index 0000000..2b3dec1 --- /dev/null +++ b/cmd/08/box/box_test.go @@ -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) + } + } +} diff --git a/cmd/08/circuitMap/circuitMap.go b/cmd/08/circuitMap/circuitMap.go new file mode 100644 index 0000000..0efe5b2 --- /dev/null +++ b/cmd/08/circuitMap/circuitMap.go @@ -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 +} diff --git a/cmd/08/circuitMap/circuitMap_test.go b/cmd/08/circuitMap/circuitMap_test.go new file mode 100644 index 0000000..437e84e --- /dev/null +++ b/cmd/08/circuitMap/circuitMap_test.go @@ -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) + } + } +} diff --git a/cmd/08/connection/connection.go b/cmd/08/connection/connection.go new file mode 100644 index 0000000..73d4b5b --- /dev/null +++ b/cmd/08/connection/connection.go @@ -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), + } +} diff --git a/cmd/08/connection/connection_test.go b/cmd/08/connection/connection_test.go new file mode 100644 index 0000000..16af46a --- /dev/null +++ b/cmd/08/connection/connection_test.go @@ -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) + } +} diff --git a/cmd/08/estimator/estimator.go b/cmd/08/estimator/estimator.go new file mode 100644 index 0000000..f3a4fd1 --- /dev/null +++ b/cmd/08/estimator/estimator.go @@ -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 +} diff --git a/cmd/08/estimator/estimator_test.go b/cmd/08/estimator/estimator_test.go new file mode 100644 index 0000000..c3723af --- /dev/null +++ b/cmd/08/estimator/estimator_test.go @@ -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) + } +} diff --git a/cmd/08/main.go b/cmd/08/main.go new file mode 100644 index 0000000..9e2c682 --- /dev/null +++ b/cmd/08/main.go @@ -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 +} diff --git a/cmd/08/main_test.go b/cmd/08/main_test.go new file mode 100644 index 0000000..2526177 --- /dev/null +++ b/cmd/08/main_test.go @@ -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) + } +}