From 934ac56ab6961cbc1aca19f4a7dded529796690f Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:28:13 -0600 Subject: [PATCH 1/4] feat: add move method to position --- internal/position/position.go | 7 +++++++ internal/position/position_test.go | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/internal/position/position.go b/internal/position/position.go index e296062..af5e679 100644 --- a/internal/position/position.go +++ b/internal/position/position.go @@ -1,9 +1,12 @@ // Package position provides types and functions to represent and manipulate positions and moves on a 2D grid. package position +import "github.com/StevanFreeborn/advent-of-code-2025/internal/move" + type Position interface { Row() int Column() int + Move(move.Move) Position } type position struct { @@ -25,3 +28,7 @@ func (p position) Row() int { func (p position) Column() int { return p.column } + +func (p position) Move(move move.Move) Position { + return From(p.row+move.NumberOfRows(), p.column+move.NumberOfColumns()) +} diff --git a/internal/position/position_test.go b/internal/position/position_test.go index 546d010..5152692 100644 --- a/internal/position/position_test.go +++ b/internal/position/position_test.go @@ -3,6 +3,7 @@ package position_test import ( "testing" + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" "github.com/StevanFreeborn/advent-of-code-2025/internal/position" ) @@ -20,3 +21,14 @@ func TestFrom(t *testing.T) { t.Errorf("expected column to be %d, got %d", column, pos.Column()) } } + +func TestMove(t *testing.T) { + expectedPos := position.From(3, 4) + startPos := position.From(2, 4) + + result := startPos.Move(move.Down) + + if result != expectedPos { + t.Errorf("expected position to be %+v, got %+v", expectedPos, result) + } +} From f3878117b9bd3a7e442bc0f31080914b6873c421 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:29:12 -0600 Subject: [PATCH 2/4] feat: add queue implementation --- internal/queue/queue.go | 55 ++++++++++++++++++++++++++++++++++++ internal/queue/queue_test.go | 1 + 2 files changed, 56 insertions(+) create mode 100644 internal/queue/queue.go create mode 100644 internal/queue/queue_test.go diff --git a/internal/queue/queue.go b/internal/queue/queue.go new file mode 100644 index 0000000..1396e66 --- /dev/null +++ b/internal/queue/queue.go @@ -0,0 +1,55 @@ +// Package queue provides a thread-safe FIFO queue implementation. +package queue + +import "sync" + +type Queue[T any] interface { + Enqueue(item T) + Dequeue() (T, bool) + IsEmpty() bool + Size() int +} + +type queue[T any] struct { + items []T + lock sync.Mutex +} + +func New[T any]() Queue[T] { + return &queue[T]{items: []T{}} +} + +func (q *queue[T]) Enqueue(item T) { + q.lock.Lock() + defer q.lock.Unlock() + + q.items = append(q.items, item) +} + +func (q *queue[T]) Dequeue() (T, bool) { + q.lock.Lock() + defer q.lock.Unlock() + + if len(q.items) == 0 { + var zero T + return zero, false + } + + item := q.items[0] + q.items = q.items[1:] + return item, true +} + +func (q *queue[T]) IsEmpty() bool { + q.lock.Lock() + defer q.lock.Unlock() + + return len(q.items) == 0 +} + +func (q *queue[T]) Size() int { + q.lock.Lock() + defer q.lock.Unlock() + + return len(q.items) +} diff --git a/internal/queue/queue_test.go b/internal/queue/queue_test.go new file mode 100644 index 0000000..b5079ed --- /dev/null +++ b/internal/queue/queue_test.go @@ -0,0 +1 @@ +package queue_test From 3560f6b53849a107bdbb7a90487023c4c08e1370 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:29:39 -0600 Subject: [PATCH 3/4] refactor: update solution to use internal packages --- cmd/07/main.go | 55 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/cmd/07/main.go b/cmd/07/main.go index 3b3ab83..1c3e250 100644 --- a/cmd/07/main.go +++ b/cmd/07/main.go @@ -2,7 +2,10 @@ package main import ( "github.com/StevanFreeborn/advent-of-code-2025/internal/file" + "github.com/StevanFreeborn/advent-of-code-2025/internal/grid" + "github.com/StevanFreeborn/advent-of-code-2025/internal/move" "github.com/StevanFreeborn/advent-of-code-2025/internal/position" + "github.com/StevanFreeborn/advent-of-code-2025/internal/queue" ) const StartCharacter = "S" @@ -10,66 +13,62 @@ const SplitterCharacter = "^" func SolvePartOne(filePath string) int { input := file.ReadAllLines(filePath) - numOfRows := len(input) - numOfCols := len(input[0]) + grid := grid.From(input) - startRow := -1 - startCol := -1 + var startPosition position.Position - for row := range numOfRows - 1 { - for col := range numOfCols - 1 { - v := string(input[row][col]) + for row := range grid.NumberOfRows() - 1 { + for col := range grid.NumberOfColumns() - 1 { + current := position.From(row, col) + v := grid.GetValueAt(current) if v == StartCharacter { - startRow = row - startCol = col + startPosition = current break } } } - if startRow < 0 || startCol < 0 { + if startPosition == nil { panic("unable to find start location") } - beamStart := position.From(startRow+1, startCol) - beamsQueue := []position.Position{ - beamStart, - } + beamStart := startPosition.Move(move.Down) + beamsQueue := queue.New[position.Position]() + beamsQueue.Enqueue(beamStart) + visited := map[position.Position]bool{} total := 0 - for len(beamsQueue) > 0 { - currentBeam := beamsQueue[0] - beamsQueue = beamsQueue[1:] + for beamsQueue.IsEmpty() == false { + currentBeam, _ := beamsQueue.Dequeue() if visited[currentBeam] { continue } - if currentBeam.Row() < 0 || currentBeam.Row() >= numOfRows { - continue - } - - if currentBeam.Column() < 0 || currentBeam.Column() >= numOfCols { + if grid.InBounds(currentBeam) == false { continue } visited[currentBeam] = true - value := string(input[currentBeam.Row()][currentBeam.Column()]) + value := grid.GetValueAt(currentBeam) if value == SplitterCharacter { total++ - rightBeam := position.From(currentBeam.Row(), currentBeam.Column()+1) - leftBeam := position.From(currentBeam.Row(), currentBeam.Column()-1) - beamsQueue = append(beamsQueue, rightBeam, leftBeam) + + rightBeam := currentBeam.Move(move.Right) + leftBeam := currentBeam.Move(move.Left) + + beamsQueue.Enqueue(rightBeam) + beamsQueue.Enqueue(leftBeam) continue } - nextBeam := position.From(currentBeam.Row()+1, currentBeam.Column()) - beamsQueue = append(beamsQueue, nextBeam) + nextBeam := currentBeam.Move(move.Down) + beamsQueue.Enqueue(nextBeam) } return total From 0a2407ec1df8ade818a1d9ec3719d2a33aa003dd Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:40:48 -0600 Subject: [PATCH 4/4] tests: add test for queue implementation --- internal/queue/queue_test.go | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/internal/queue/queue_test.go b/internal/queue/queue_test.go index b5079ed..a46d1d8 100644 --- a/internal/queue/queue_test.go +++ b/internal/queue/queue_test.go @@ -1 +1,87 @@ package queue_test + +import ( + "testing" + + "github.com/StevanFreeborn/advent-of-code-2025/internal/queue" +) + +func TestQueue_EnqueueDequeue(t *testing.T) { + q := queue.New[int]() + + q.Enqueue(1) + q.Enqueue(2) + q.Enqueue(3) + + expectedValues := []int{1, 2, 3} + + for _, expected := range expectedValues { + value, ok := q.Dequeue() + + if !ok { + t.Errorf("expected dequeue to be successful, but it failed") + } + + if value != expected { + t.Errorf("expected value to be %d, got %d", expected, value) + } + + } + + _, ok := q.Dequeue() + + if ok { + t.Errorf("expected dequeue to fail on empty queue, but it succeeded") + } + + if !q.IsEmpty() { + t.Errorf("expected queue to be empty, but it is not") + } +} + +func TestQueue_Size(t *testing.T) { + q := queue.New[string]() + + if q.Size() != 0 { + t.Errorf("expected size to be 0, got %d", q.Size()) + } + + q.Enqueue("a") + q.Enqueue("b") + + if q.Size() != 2 { + t.Errorf("expected size to be 2, got %d", q.Size()) + } + + q.Dequeue() + + if q.Size() != 1 { + t.Errorf("expected size to be 1, got %d", q.Size()) + } +} + +func TestQueue_ConcurrentAccess(t *testing.T) { + q := queue.New[int]() + done := make(chan bool) + numGoroutines := 100 + numItemsPerGoroutine := 100 + + for i := range numGoroutines { + go func(start int) { + for j := range numItemsPerGoroutine { + q.Enqueue(start + j) + } + done <- true + }(i * numItemsPerGoroutine) + } + + for range numGoroutines { + <-done + } + + expectedSize := numGoroutines * numItemsPerGoroutine + + if q.Size() != expectedSize { + t.Errorf("expected size to be %d, got %d", expectedSize, q.Size()) + } +}