Merge pull request #23 from StevanFreeborn/stevanfreeborn/refactor/clean-up-day-nine-part-two

refactor: edge and rectangle in separate packages
This commit is contained in:
Stevan Freeborn
2025-12-19 19:02:05 -06:00
committed by GitHub
5 changed files with 373 additions and 68 deletions
+97
View File
@@ -0,0 +1,97 @@
// Package edge provides a model and methods for representing and manipulating edges.
package edge
import (
"math"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/09/rectangle"
"github.com/StevanFreeborn/advent-of-code-2025/internal/position"
)
type Edge interface {
Start() position.Position
End() position.Position
VerticallyContains(position.Position) bool
IntersectsHorizontalLineAt(position.Position) bool
Intersects(rect rectangle.Rectangle) bool
}
type edge struct {
start position.Position
end position.Position
totalHeight int
totalWidth int
minRow float64
maxRow float64
minColumn float64
maxColumn float64
}
func From(start position.Position, end position.Position) Edge {
totalHeight := int(math.Abs(float64(end.Row() - start.Row())))
totalWidth := int(math.Abs(float64(end.Column() - start.Column())))
minRow := math.Min(float64(start.Row()), float64(end.Row()))
maxRow := math.Max(float64(start.Row()), float64(end.Row()))
minColumn := math.Min(float64(start.Column()), float64(end.Column()))
maxColumn := math.Max(float64(start.Column()), float64(end.Column()))
return edge{
start: start,
end: end,
totalHeight: totalHeight,
totalWidth: totalWidth,
minRow: minRow,
maxRow: maxRow,
minColumn: minColumn,
maxColumn: maxColumn,
}
}
func (e edge) Start() position.Position {
return e.start
}
func (e edge) End() position.Position {
return e.end
}
func (e edge) VerticallyContains(p position.Position) bool {
return (e.start.Row() <= p.Row()) && (p.Row() <= e.end.Row())
}
func (e edge) IntersectsHorizontalLineAt(p position.Position) bool {
centerPercentageOfEdgeHeight := int(float64(p.Row()-e.start.Row()) / float64(e.totalHeight))
edgeColumnAtCenterRow := e.start.Column() + centerPercentageOfEdgeHeight*e.totalWidth
return p.Row() <= edgeColumnAtCenterRow
}
func (e edge) Intersects(rect rectangle.Rectangle) bool {
isVerticalEdge := e.start.Column() == e.end.Column()
if isVerticalEdge {
isRectBetweenEdgeColumns := e.start.Column() > rect.MinColumn() && e.start.Column() < rect.MaxColumn()
if isRectBetweenEdgeColumns {
overlapStart := int(math.Max(e.minRow, float64(rect.MinRow())))
overlapEnd := int(math.Min(e.maxRow, float64(rect.MaxRow())))
if overlapEnd > overlapStart {
return true
}
}
} else {
isRectBetweenEdgeRows := e.start.Row() > rect.MinRow() && e.start.Row() < rect.MaxRow()
if isRectBetweenEdgeRows {
overlapStart := int(math.Max(e.minColumn, float64(rect.MinColumn())))
overlapEnd := int(math.Min(e.maxColumn, float64(rect.MaxColumn())))
if overlapEnd > overlapStart {
return true
}
}
}
return false
}
+71
View File
@@ -0,0 +1,71 @@
package edge_test
import (
"testing"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/09/edge"
"github.com/StevanFreeborn/advent-of-code-2025/internal/position"
)
func TestFrom(t *testing.T) {
start := position.From(1, 2)
end := position.From(3, 4)
e := edge.From(start, end)
if e.Start() != start {
t.Errorf("Expected start position %v, got %v", start, e.Start())
}
if e.End() != end {
t.Errorf("Expected end position %v, got %v", end, e.End())
}
}
func TestVerticallyContains(t *testing.T) {
start := position.From(1, 2)
end := position.From(3, 4)
e := edge.From(start, end)
tests := []struct {
p position.Position
expected bool
}{
{position.From(2, 3), true},
{position.From(1, 2), true},
{position.From(3, 4), true},
{position.From(0, 0), false},
{position.From(4, 5), false},
}
for _, test := range tests {
result := e.VerticallyContains(test.p)
if result != test.expected {
t.Errorf("VerticallyContains(%v) = %v; want %v", test.p, result, test.expected)
}
}
}
func TestIntersectsHorizontalLineAt(t *testing.T) {
start := position.From(1, 2)
end := position.From(3, 4)
e := edge.From(start, end)
tests := []struct {
p position.Position
expected bool
}{
{position.From(2, 3), true},
{position.From(1, 2), true},
{position.From(3, 4), true},
}
for _, test := range tests {
result := e.IntersectsHorizontalLineAt(test.p)
if result != test.expected {
t.Errorf("IntersectsHorizontalLineAt(%v) = %v; want %v", test.p, result, test.expected)
}
}
}
+17 -68
View File
@@ -1,10 +1,11 @@
package main package main
import ( import (
"math"
"strconv" "strconv"
"strings" "strings"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/09/edge"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/09/rectangle"
"github.com/StevanFreeborn/advent-of-code-2025/internal/file" "github.com/StevanFreeborn/advent-of-code-2025/internal/file"
"github.com/StevanFreeborn/advent-of-code-2025/internal/position" "github.com/StevanFreeborn/advent-of-code-2025/internal/position"
) )
@@ -27,14 +28,10 @@ func SolvePartOne(filePath string) int {
for j := i + 1; j < numberOfPositions; j++ { for j := i + 1; j < numberOfPositions; j++ {
start := positions[i] start := positions[i]
end := positions[j] end := positions[j]
rect := rectangle.From(start, end)
width := int(math.Abs(float64(end.Column()-start.Column())) + 1) if rect.Area() > largestArea {
height := int(math.Abs(float64(end.Row()-start.Row())) + 1) largestArea = rect.Area()
a := width * height
if a > largestArea {
largestArea = a
} }
} }
} }
@@ -42,8 +39,6 @@ func SolvePartOne(filePath string) int {
return largestArea return largestArea
} }
// TODO: Refactor below so more understandable
func SolvePartTwo(filePath string) int { func SolvePartTwo(filePath string) int {
positions := []position.Position{} positions := []position.Position{}
@@ -62,41 +57,21 @@ func SolvePartTwo(filePath string) int {
for j := i + 1; j < numberOfPositions; j++ { for j := i + 1; j < numberOfPositions; j++ {
start := positions[i] start := positions[i]
end := positions[j] end := positions[j]
rect := rectangle.From(start, end)
rectMinRow := int(math.Min(float64(start.Row()), float64(end.Row()))) if rect.Area() <= largestArea {
rectMaxRow := int(math.Max(float64(start.Row()), float64(end.Row())))
rectMinCol := int(math.Min(float64(start.Column()), float64(end.Column())))
rectMaxCol := int(math.Max(float64(start.Column()), float64(end.Column())))
width := int(math.Abs(float64(rectMaxCol-rectMinCol)) + 1)
height := int(math.Abs(float64(rectMaxRow-rectMinRow)) + 1)
a := width * height
if a <= largestArea {
continue continue
} }
centerRow := int((rectMaxRow + rectMinRow) / 2)
centerCol := int((rectMaxCol + rectMinCol) / 2)
center := position.From(centerRow, centerCol)
centerIsInside := false centerIsInside := false
for i := range numberOfPositions { for i := range numberOfPositions {
edgeStart := positions[i] edgeStart := positions[i]
edgeEndIndex := (i + 1) % numberOfPositions edgeEndIndex := (i + 1) % numberOfPositions
edgeEnd := positions[edgeEndIndex] edgeEnd := positions[edgeEndIndex]
edge := edge.From(edgeStart, edgeEnd)
isCenterVerticallyBetweenEdge := (edgeStart.Row() <= center.Row()) && (center.Row() <= edgeEnd.Row()) if edge.VerticallyContains(rect.Center()) && edge.IntersectsHorizontalLineAt(rect.Center()) {
edgeTotalHeight := int(math.Abs(float64(edgeEnd.Row() - edgeStart.Row())))
edgeTotalWidth := int(math.Abs(float64(edgeEnd.Column() - edgeStart.Column())))
centerPercentageOfEdgeHeight := int(float64(center.Row()-edgeStart.Row()) / float64(edgeTotalHeight))
edgeColumnAtCenterRow := edgeStart.Column() + centerPercentageOfEdgeHeight*edgeTotalWidth
isCenterHorizontallyToTheLeft := center.Row() <= edgeColumnAtCenterRow
if isCenterVerticallyBetweenEdge && isCenterHorizontallyToTheLeft {
centerIsInside = !centerIsInside centerIsInside = !centerIsInside
} }
} }
@@ -108,40 +83,14 @@ func SolvePartTwo(filePath string) int {
isIntersectedByEdge := false isIntersectedByEdge := false
for i := range numberOfPositions { for i := range numberOfPositions {
currPos := positions[i] edgeStart := positions[i]
nextPosIndex := (i + 1) % numberOfPositions edgeEndIndex := (i + 1) % numberOfPositions
nextPos := positions[nextPosIndex] edgeEnd := positions[edgeEndIndex]
edge := edge.From(edgeStart, edgeEnd)
if currPos.Column() == nextPos.Column() { if edge.Intersects(rect) {
col := currPos.Column() isIntersectedByEdge = true
break
if col > rectMinCol && col < rectMaxCol {
edgeMinRow := math.Min(float64(currPos.Row()), float64(nextPos.Row()))
edgeMaxRow := math.Max(float64(currPos.Row()), float64(nextPos.Row()))
overlapStart := int(math.Max(edgeMinRow, float64(rectMinRow)))
overlapEnd := int(math.Min(edgeMaxRow, float64(rectMaxRow)))
if overlapEnd > overlapStart {
isIntersectedByEdge = true
break
}
}
} else {
row := currPos.Row()
if row > rectMinRow && row < rectMaxRow {
edgeMinCol := math.Min(float64(currPos.Column()), float64(nextPos.Column()))
edgeMaxCol := math.Max(float64(currPos.Column()), float64(nextPos.Column()))
overlapStart := int(math.Max(edgeMinCol, float64(rectMinCol)))
overlapEnd := int(math.Min(edgeMaxCol, float64(rectMaxCol)))
if overlapEnd > overlapStart {
isIntersectedByEdge = true
break
}
}
} }
} }
@@ -149,7 +98,7 @@ func SolvePartTwo(filePath string) int {
continue continue
} }
largestArea = a largestArea = rect.Area()
} }
} }
+83
View File
@@ -0,0 +1,83 @@
// Package rectangle provides a model and methods for representing and manipulating rectangles.
package rectangle
import (
"math"
"github.com/StevanFreeborn/advent-of-code-2025/internal/position"
)
type Rectangle interface {
Corners() []position.Position
MaxRow() int
MaxColumn() int
MinRow() int
MinColumn() int
Area() int
Center() position.Position
}
type rectangle struct {
corners []position.Position
maxRow int
maxColumn int
minRow int
minColumn int
area int
center position.Position
}
func From(cornerOne position.Position, cornerTwo position.Position) Rectangle {
thirdCorner := position.From(cornerOne.Row(), cornerTwo.Column())
fourthCorner := position.From(cornerTwo.Row(), cornerOne.Column())
corners := []position.Position{cornerOne, cornerTwo, thirdCorner, fourthCorner}
maxRow := math.Max(float64(cornerOne.Row()), float64(cornerTwo.Row()))
maxColumn := math.Max(float64(cornerOne.Column()), float64(cornerTwo.Column()))
minRow := math.Min(float64(cornerOne.Row()), float64(cornerTwo.Row()))
minColumn := math.Min(float64(cornerOne.Column()), float64(cornerTwo.Column()))
area := int((maxRow - minRow + 1) * (maxColumn - minColumn + 1))
centerRow := (maxRow + minRow) / 2
centerColumn := (maxColumn + minColumn) / 2
center := position.From(int(centerRow), int(centerColumn))
return rectangle{
corners: corners,
maxRow: int(maxRow),
maxColumn: int(maxColumn),
minRow: int(minRow),
minColumn: int(minColumn),
area: area,
center: center,
}
}
func (r rectangle) Corners() []position.Position {
return r.corners
}
func (r rectangle) MaxRow() int {
return r.maxRow
}
func (r rectangle) MinRow() int {
return r.minRow
}
func (r rectangle) MaxColumn() int {
return r.maxColumn
}
func (r rectangle) MinColumn() int {
return r.minColumn
}
func (r rectangle) Area() int {
return r.area
}
func (r rectangle) Center() position.Position {
return r.center
}
+105
View File
@@ -0,0 +1,105 @@
package rectangle_test
import (
"slices"
"testing"
"github.com/StevanFreeborn/advent-of-code-2025/cmd/09/rectangle"
"github.com/StevanFreeborn/advent-of-code-2025/internal/position"
)
func TestFrom(t *testing.T) {
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
expectedCorners := []position.Position{
cornerOne,
cornerTwo,
position.From(1, 4),
position.From(3, 2),
}
result := rectangle.From(cornerOne, cornerTwo)
if slices.Equal(result.Corners(), expectedCorners) == false {
t.Errorf("Expected corners %v, but got %v", expectedCorners, result.Corners())
}
}
func TestMaxRow(t *testing.T) {
expectedMaxRow := 3
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).MaxRow()
if result != expectedMaxRow {
t.Errorf("Expected max row %d, but got %d", expectedMaxRow, result)
}
}
func TestMinRow(t *testing.T) {
expectedMinRow := 1
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).MinRow()
if result != expectedMinRow {
t.Errorf("Expected min row %d, but got %d", expectedMinRow, result)
}
}
func TestMaxColumn(t *testing.T) {
expectedMaxColumn := 4
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).MaxColumn()
if result != expectedMaxColumn {
t.Errorf("Expected max column %d, but got %d", expectedMaxColumn, result)
}
}
func TestMinColumn(t *testing.T) {
expectedMinColumn := 2
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).MinColumn()
if result != expectedMinColumn {
t.Errorf("Expected min column %d, but got %d", expectedMinColumn, result)
}
}
func TestArea(t *testing.T) {
expectedArea := 9
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).Area()
if result != expectedArea {
t.Errorf("Expected area %d, but got %d", expectedArea, result)
}
}
func TestCenter(t *testing.T) {
expectedCenter := position.From(2, 3)
cornerOne := position.From(1, 2)
cornerTwo := position.From(3, 4)
result := rectangle.From(cornerOne, cornerTwo).Center()
if result != expectedCenter {
t.Errorf("Expected center %v, but got %v", expectedCenter, result)
}
}