Skip to content

Commit a043348

Browse files
committed
feat(linked): add linked queue implementation with tests
1 parent 4e91cb0 commit a043348

File tree

7 files changed

+546
-13
lines changed

7 files changed

+546
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## v1.2.1 (2023-05-08)
22

3+
### Fix
4+
35
- fix a bug caused by not calling the (*sync.Cond).Wait() method in a loop
46

57
## v1.2.0 (2023-04-14)

README.md

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Benchmarks and Example tests can be found in this package.
3333
* [Blocking Queue](#blocking-queue)
3434
* [Priority Queue](#priority-queue)
3535
* [Circular Queue](#circular-queue)
36+
* [Linked Queue](#linked-queue)
3637
* [Benchmarks](#benchmarks-)
3738
<!-- TOC -->
3839

@@ -221,18 +222,64 @@ func main() {
221222
}
222223
```
223224

225+
### Linked Queue
226+
227+
A linked queue, implemented as a singly linked list, offering O(1)
228+
time complexity for enqueue and dequeue operations. The queue maintains pointers
229+
to both the head (front) and tail (end) of the list for efficient operations
230+
without the need for traversal.
231+
232+
```go
233+
package main
234+
235+
import (
236+
"fmt"
237+
238+
"github.com/adrianbrad/queue"
239+
)
240+
241+
func main() {
242+
elems := []int{2, 3, 4}
243+
244+
circularQueue := queue.NewLinked(elems)
245+
246+
containsTwo := circularQueue.Contains(2)
247+
fmt.Println(containsTwo) // true
248+
249+
size := circularQueue.Size()
250+
fmt.Println(size) // 3
251+
252+
empty := circularQueue.IsEmpty()
253+
fmt.Println(empty) // false
254+
255+
if err := circularQueue.Offer(1); err != nil {
256+
// handle err
257+
}
258+
259+
elem, err := circularQueue.Get()
260+
if err != nil {
261+
// handle err
262+
}
263+
264+
fmt.Printf("elem: %d\n", elem) // elem: 2
265+
}
266+
```
267+
224268
## Benchmarks
225269

226-
Results as of April 2023.
270+
Results as of October 2023.
227271

228272
```text
229-
BenchmarkBlockingQueue/Peek-12 72900492 18.92 ns/op 0 B/op 0 allocs/op
230-
BenchmarkBlockingQueue/Get_Offer-12 14937858 95.08 ns/op 41 B/op 0 allocs/op
231-
BenchmarkBlockingQueue/Offer-12 26680512 51.81 ns/op 45 B/op 0 allocs/op
232-
BenchmarkCircularQueue/Peek-12 73749498 16.24 ns/op 0 B/op 0 allocs/op
233-
BenchmarkCircularQueue/Get_Offer-12 18768650 63.02 ns/op 0 B/op 0 allocs/op
234-
BenchmarkCircularQueue/Offer-12 38328231 37.57 ns/op 0 B/op 0 allocs/op
235-
BenchmarkPriorityQueue/Peek-12 75156879 15.79 ns/op 0 B/op 0 allocs/op
236-
BenchmarkPriorityQueue/Get_Offer-12 17643837 68.65 ns/op 0 B/op 0 allocs/op
237-
BenchmarkPriorityQueue/Offer-12 20506784 57.43 ns/op 54 B/op 0 allocs/op
273+
BenchmarkBlockingQueue/Peek-8 84873882 13.98 ns/op 0 B/op 0 allocs/op
274+
BenchmarkBlockingQueue/Get_Offer-8 27135865 47.00 ns/op 44 B/op 0 allocs/op
275+
BenchmarkBlockingQueue/Offer-8 53750395 25.40 ns/op 43 B/op 0 allocs/op
276+
BenchmarkCircularQueue/Peek-8 86001980 13.76 ns/op 0 B/op 0 allocs/op
277+
BenchmarkCircularQueue/Get_Offer-8 32379159 36.83 ns/op 0 B/op 0 allocs/op
278+
BenchmarkCircularQueue/Offer-8 63956366 18.77 ns/op 0 B/op 0 allocs/op
279+
BenchmarkLinkedQueue/Peek-8 1000000000 0.4179 ns/op 0 B/op 0 allocs/op
280+
BenchmarkLinkedQueue/Get_Offer-8 61257436 18.48 ns/op 16 B/op 1 allocs/op
281+
BenchmarkLinkedQueue/Offer-8 38975062 30.74 ns/op 16 B/op 1 allocs/op
282+
BenchmarkPriorityQueue/Peek-8 86633734 14.02 ns/op 0 B/op 0 allocs/op
283+
BenchmarkPriorityQueue/Get_Offer-8 29347177 39.88 ns/op 0 B/op 0 allocs/op
284+
BenchmarkPriorityQueue/Offer-8 40117958 31.37 ns/op 54 B/op 0 allocs/op
238285
```

doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@
1212
// A circular queue, which is a queue that uses a fixed-size slice as
1313
// if it were connected end-to-end. When the queue is full, adding a new element to the queue
1414
// overwrites the oldest element.
15+
//
16+
// A linked queue, implemented as a singly linked list, offering O(1)
17+
// time complexity for enqueue and dequeue operations. The queue maintains pointers
18+
// to both the head (front) and tail (end) of the list for efficient operations
19+
// without the need for traversal.
1520
package queue

example_linked_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package queue_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/adrianbrad/queue"
7+
)
8+
9+
func ExampleLinked() {
10+
elems := []int{2, 4, 1}
11+
12+
priorityQueue := queue.NewLinked(
13+
elems,
14+
)
15+
16+
containsTwo := priorityQueue.Contains(2)
17+
fmt.Println("Contains 2:", containsTwo)
18+
19+
size := priorityQueue.Size()
20+
fmt.Println("Size:", size)
21+
22+
if err := priorityQueue.Offer(3); err != nil {
23+
fmt.Println("Offer err: ", err)
24+
return
25+
}
26+
27+
empty := priorityQueue.IsEmpty()
28+
fmt.Println("Empty before clear:", empty)
29+
30+
clearElems := priorityQueue.Clear()
31+
fmt.Println("Clear:", clearElems)
32+
33+
empty = priorityQueue.IsEmpty()
34+
fmt.Println("Empty after clear:", empty)
35+
36+
if err := priorityQueue.Offer(5); err != nil {
37+
fmt.Println("Offer err: ", err)
38+
return
39+
}
40+
41+
elem, err := priorityQueue.Get()
42+
if err != nil {
43+
fmt.Println("Get err: ", err)
44+
return
45+
}
46+
47+
fmt.Println("Get:", elem)
48+
49+
// Output:
50+
// Contains 2: true
51+
// Size: 3
52+
// Empty before clear: false
53+
// Clear: [2 4 1 3]
54+
// Empty after clear: true
55+
// Get: 5
56+
}

linked.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package queue
2+
3+
var _ Queue[any] = (*Linked[any])(nil)
4+
5+
// node is an individual element of the linked list.
6+
type node[T any] struct {
7+
value T
8+
next *node[T]
9+
}
10+
11+
// Linked represents a data structure representing a queue that uses a
12+
// linked list for its internal storage.
13+
// ! The Linked Queue is not thread safe.
14+
type Linked[T comparable] struct {
15+
head *node[T] // first node of the queue.
16+
tail *node[T] // last node of the queue.
17+
size int // number of elements in the queue.
18+
// nolint: revive
19+
initialElements []T // initial elements with which the queue was created, allowing for a reset to its original state if needed.
20+
}
21+
22+
// NewLinked creates a new Linked containing the given elements.
23+
func NewLinked[T comparable](elements []T) *Linked[T] {
24+
queue := &Linked[T]{
25+
head: nil,
26+
tail: nil,
27+
size: 0,
28+
initialElements: make([]T, len(elements)),
29+
}
30+
31+
copy(queue.initialElements, elements)
32+
33+
for _, element := range elements {
34+
_ = queue.Offer(element)
35+
}
36+
37+
return queue
38+
}
39+
40+
// Get retrieves and removes the head of the queue.
41+
func (q *Linked[T]) Get() (elem T, _ error) {
42+
if q.IsEmpty() {
43+
return elem, ErrNoElementsAvailable
44+
}
45+
46+
value := q.head.value
47+
q.head = q.head.next
48+
q.size--
49+
50+
if q.IsEmpty() {
51+
q.tail = nil
52+
}
53+
54+
return value, nil
55+
}
56+
57+
// Offer inserts the element into the queue.
58+
func (q *Linked[T]) Offer(value T) error {
59+
newNode := &node[T]{value: value}
60+
61+
if q.IsEmpty() {
62+
q.head = newNode
63+
} else {
64+
q.tail.next = newNode
65+
}
66+
67+
q.tail = newNode
68+
q.size++
69+
70+
return nil
71+
}
72+
73+
// Reset sets the queue to its initial state.
74+
func (q *Linked[T]) Reset() {
75+
q.head = nil
76+
q.tail = nil
77+
q.size = 0
78+
79+
for _, element := range q.initialElements {
80+
_ = q.Offer(element)
81+
}
82+
}
83+
84+
// Contains returns true if the queue contains the element.
85+
func (q *Linked[T]) Contains(value T) bool {
86+
current := q.head
87+
for current != nil {
88+
if current.value == value {
89+
return true
90+
}
91+
92+
current = current.next
93+
}
94+
95+
return false
96+
}
97+
98+
// Peek retrieves but does not remove the head of the queue.
99+
func (q *Linked[T]) Peek() (elem T, _ error) {
100+
if q.IsEmpty() {
101+
return elem, ErrNoElementsAvailable
102+
}
103+
104+
return q.head.value, nil
105+
}
106+
107+
// Size returns the number of elements in the queue.
108+
func (q *Linked[T]) Size() int {
109+
return q.size
110+
}
111+
112+
// IsEmpty returns true if the queue is empty, false otherwise.
113+
func (q *Linked[T]) IsEmpty() bool {
114+
return q.size == 0
115+
}
116+
117+
// Iterator returns a channel that will be filled with the elements.
118+
// It removes the elements from the queue.
119+
func (q *Linked[T]) Iterator() <-chan T {
120+
ch := make(chan T)
121+
122+
elems := q.Clear()
123+
124+
go func() {
125+
for _, e := range elems {
126+
ch <- e
127+
}
128+
129+
close(ch)
130+
}()
131+
132+
return ch
133+
}
134+
135+
// Clear removes and returns all elements from the queue.
136+
func (q *Linked[T]) Clear() []T {
137+
elements := make([]T, 0, q.size)
138+
139+
current := q.head
140+
for current != nil {
141+
elements = append(elements, current.value)
142+
next := current.next
143+
current = next
144+
}
145+
146+
// Clear the queue
147+
q.head = nil
148+
q.tail = nil
149+
q.size = 0
150+
151+
return elements
152+
}

0 commit comments

Comments
 (0)