diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31898fb..1a5bd4d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: commitizen - repo: https://github.com/golangci/golangci-lint - rev: v1.63.3 + rev: v1.63.4 hooks: - id: golangci-lint name: golangci-lint diff --git a/blocking.go b/blocking.go index 6f56983..b65f81a 100644 --- a/blocking.go +++ b/blocking.go @@ -1,6 +1,8 @@ package queue import ( + "encoding/json" + "fmt" "sync" ) @@ -295,3 +297,26 @@ func (bq *Blocking[T]) get() (v T, _ error) { return elem, nil } + +// MarshalJSON serializes the Blocking queue to JSON. +func (bq *Blocking[T]) MarshalJSON() ([]byte, error) { + bq.lock.RLock() + + if bq.IsEmpty() { + bq.lock.RUnlock() + return []byte("[]"), nil + } + + // Extract elements from `elements` starting at `elementsIndex`. + elements := bq.elements[bq.elementsIndex:] + + bq.lock.RUnlock() + + // Marshal the slice of elements into JSON. + data, err := json.Marshal(elements) + if err != nil { + return nil, fmt.Errorf("failed to marshal blocking queue: %w", err) + } + + return data, nil +} diff --git a/blocking_test.go b/blocking_test.go index 07ae732..b2851d4 100644 --- a/blocking_test.go +++ b/blocking_test.go @@ -1,6 +1,8 @@ package queue_test import ( + "bytes" + "encoding/json" "errors" "fmt" "reflect" @@ -704,6 +706,24 @@ func TestBlocking(t *testing.T) { } }) }) + + t.Run("MarshalJSON", func(t *testing.T) { + t.Parallel() + + elems := []int{3, 2, 1} + + q := queue.NewBlocking(elems) + + marshaled, err := json.Marshal(q) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + expectedMarshaled := []byte(`[3,2,1]`) + if !bytes.Equal(expectedMarshaled, marshaled) { + t.Fatalf("expected marshaled to be %s, got %s", expectedMarshaled, marshaled) + } + }) } func testResetOnMultipleRoutinesFunc[T comparable]( diff --git a/circular.go b/circular.go index c142574..2efcb73 100644 --- a/circular.go +++ b/circular.go @@ -1,6 +1,7 @@ package queue import ( + "encoding/json" "sync" ) @@ -231,3 +232,25 @@ func (q *Circular[T]) get() (v T, _ error) { func (q *Circular[T]) isEmpty() bool { return q.size == 0 } + +// MarshalJSON serializes the Circular queue to JSON. +func (q *Circular[T]) MarshalJSON() ([]byte, error) { + q.lock.RLock() + + if q.isEmpty() { + q.lock.RUnlock() + return []byte("[]"), nil + } + + // Collect elements in logical order from head to tail. + elements := make([]T, 0, q.size) + + for i := 0; i < q.size; i++ { + index := (q.head + i) % len(q.elems) + elements = append(elements, q.elems[index]) + } + + q.lock.RUnlock() + + return json.Marshal(elements) +} diff --git a/circular_test.go b/circular_test.go index 17225e2..98d616d 100644 --- a/circular_test.go +++ b/circular_test.go @@ -1,6 +1,8 @@ package queue_test import ( + "bytes" + "encoding/json" "errors" "reflect" "testing" @@ -290,6 +292,24 @@ func TestCircular(t *testing.T) { t.Fatalf("expected elements to be %v, got %v", elems, iterElems) } }) + + t.Run("MarshalJSON", func(t *testing.T) { + t.Parallel() + + elems := []int{3, 2, 1} + + q := queue.NewCircular(elems, 4) + + marshaled, err := json.Marshal(q) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + expectedMarshaled := []byte(`[3,2,1]`) + if !bytes.Equal(expectedMarshaled, marshaled) { + t.Fatalf("expected marshaled to be %s, got %s", expectedMarshaled, marshaled) + } + }) } func BenchmarkCircularQueue(b *testing.B) { diff --git a/linked.go b/linked.go index 7a7d01a..c18a320 100644 --- a/linked.go +++ b/linked.go @@ -1,6 +1,7 @@ package queue import ( + "encoding/json" "sync" ) @@ -189,3 +190,22 @@ func (lq *Linked[T]) Clear() []T { return elements } + +// MarshalJSON serializes the Linked queue to JSON. +func (lq *Linked[T]) MarshalJSON() ([]byte, error) { + lq.lock.RLock() + defer lq.lock.RUnlock() + + // Traverse the linked list and collect elements. + elements := make([]T, 0, lq.size) + + current := lq.head + + for current != nil { + elements = append(elements, current.value) + current = current.next + } + + // Marshal the elements slice to JSON. + return json.Marshal(elements) +} diff --git a/linked_test.go b/linked_test.go index 002d81d..708745f 100644 --- a/linked_test.go +++ b/linked_test.go @@ -1,6 +1,8 @@ package queue_test import ( + "bytes" + "encoding/json" "errors" "reflect" "testing" @@ -231,6 +233,24 @@ func TestLinked(t *testing.T) { t.Fatalf("expected elements to be %v, got %v", elems, iterElems) } }) + + t.Run("MarshalJSON", func(t *testing.T) { + t.Parallel() + + elems := []int{3, 2, 1} + + q := queue.NewLinked(elems) + + marshaled, err := json.Marshal(q) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + expectedMarshaled := []byte(`[3,2,1]`) + if !bytes.Equal(expectedMarshaled, marshaled) { + t.Fatalf("expected marshaled to be %s, got %s", expectedMarshaled, marshaled) + } + }) } func BenchmarkLinkedQueue(b *testing.B) { diff --git a/priority.go b/priority.go index 46f4407..f93e241 100644 --- a/priority.go +++ b/priority.go @@ -2,6 +2,7 @@ package queue import ( "container/heap" + "encoding/json" "sort" "sync" ) @@ -269,3 +270,32 @@ func (pq *Priority[T]) Size() int { return pq.elements.Len() } + +// MarshalJSON serializes the Priority queue to JSON. +func (pq *Priority[T]) MarshalJSON() ([]byte, error) { + pq.lock.RLock() + + // Create a temporary copy of the heap to extract elements in order. + tempHeap := &priorityHeap[T]{ + elems: make([]T, len(pq.elements.elems)), + lessFunc: pq.elements.lessFunc, + } + + copy(tempHeap.elems, pq.elements.elems) + + pq.lock.RUnlock() + + heap.Init(tempHeap) + + output := make([]T, len(tempHeap.elems)) + + i := 0 + + for tempHeap.Len() > 0 { + // nolint: forcetypeassert, revive + output[i] = tempHeap.Pop().(T) + i++ + } + + return json.Marshal(output) +} diff --git a/priority_test.go b/priority_test.go index c72e4d8..d23095b 100644 --- a/priority_test.go +++ b/priority_test.go @@ -1,6 +1,8 @@ package queue_test import ( + "bytes" + "encoding/json" "errors" "reflect" "sort" @@ -362,6 +364,24 @@ func TestPriority(t *testing.T) { } }) }) + + t.Run("MarshalJSON", func(t *testing.T) { + t.Parallel() + + elems := []int{3, 2, 1} + + priorityQueue := queue.NewPriority(elems, lessAscending) + + marshaled, err := json.Marshal(priorityQueue) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + expectedMarshaled := []byte(`[3,2,1]`) + if !bytes.Equal(expectedMarshaled, marshaled) { + t.Fatalf("expected marshaled to be %s, got %s", expectedMarshaled, marshaled) + } + }) } func FuzzPriority(f *testing.F) { @@ -411,6 +431,22 @@ func FuzzPriority(f *testing.F) { } func BenchmarkPriorityQueue(b *testing.B) { + b.Run("MarshalJSON", func(b *testing.B) { + priorityQueue := queue.NewPriority[int]( + []int{2, 3, 1, 5, 4}, + func(elem, otherElem int) bool { + return elem < otherElem + }, + ) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i <= b.N; i++ { + _, _ = priorityQueue.MarshalJSON() + } + }) + b.Run("Peek", func(b *testing.B) { priorityQueue := queue.NewPriority([]int{1}, func(elem, otherElem int) bool { return elem < otherElem