From fb6167fc49c3455e3e8d141244549dec5ea78875 Mon Sep 17 00:00:00 2001 From: Werner Strydom Date: Sun, 10 Dec 2023 07:17:48 -0800 Subject: [PATCH] Adds Queue --- concurrent/queue.go | 85 ++++++++++++++++++++++++++++++++++++++++ concurrent/queue_test.go | 76 +++++++++++++++++++++++++++++++++++ list.go | 1 + queue.go | 19 +++++++++ unsafe/queue.go | 58 +++++++++++++++++++++++++++ unsafe/queue_test.go | 76 +++++++++++++++++++++++++++++++++++ 6 files changed, 315 insertions(+) create mode 100644 concurrent/queue.go create mode 100644 concurrent/queue_test.go create mode 100644 queue.go create mode 100644 unsafe/queue.go create mode 100644 unsafe/queue_test.go diff --git a/concurrent/queue.go b/concurrent/queue.go new file mode 100644 index 0000000..20ff1e5 --- /dev/null +++ b/concurrent/queue.go @@ -0,0 +1,85 @@ +package concurrent + +import ( + "errors" + "sync" +) + +var ( + ErrQueueEmpty = errors.New("queue is empty") +) + +// Queue is a first-in-first-out (FIFO) data structure that is safe for concurrent use +type Queue[T any] struct { + items []T + lock *sync.RWMutex +} + +// NewQueue creates a new Queue +func NewQueue[T any](items ...T) *Queue[T] { + return &Queue[T]{ + items: items, + lock: &sync.RWMutex{}, + } +} + +// Dequeue removes an element from the queue. If the queue is empty, an error is returned +func (q *Queue[T]) Dequeue() (T, error) { + q.lock.Lock() + defer q.lock.Unlock() + + var zero T + if len(q.items) == 0 { + return zero, ErrQueueEmpty + } + + item := q.items[0] + q.items = q.items[1:] + return item, nil +} + +// Enqueue adds an element to the queue +func (q *Queue[T]) Enqueue(item T) error { + q.lock.Lock() + defer q.lock.Unlock() + + q.items = append(q.items, item) + return nil +} + +// IsEmpty returns true if the queue is empty +func (q *Queue[T]) IsEmpty() bool { + q.lock.RLock() + defer q.lock.RUnlock() + + return len(q.items) == 0 +} + +// Peek returns the element at the front of the queue without removing it. If the queue is empty, an error is returned +func (q *Queue[T]) Peek() (T, error) { + q.lock.RLock() + defer q.lock.RUnlock() + + var zero T + if len(q.items) == 0 { + return zero, ErrQueueEmpty + } + + return q.items[0], nil +} + +// Len returns the number of elements in the queue +func (q *Queue[T]) Len() int { + q.lock.RLock() + defer q.lock.RUnlock() + + return len(q.items) +} + +// Clear removes all items from the queue +func (q *Queue[T]) Clear() { + q.lock.Lock() + defer q.lock.Unlock() + + q.items = []T{} +} diff --git a/concurrent/queue_test.go b/concurrent/queue_test.go new file mode 100644 index 0000000..ec2f9c8 --- /dev/null +++ b/concurrent/queue_test.go @@ -0,0 +1,76 @@ +package concurrent + +import "fmt" + +func ExampleNewQueue() { + q := NewQueue[int](1, 2, 3) + _ = q.Enqueue(4) + _ = q.Enqueue(5) + for !q.IsEmpty() { + item, _ := q.Dequeue() + fmt.Println(item) + } + // Output: + // 1 + // 2 + // 3 + // 4 + // 5 +} + +func ExampleQueue_Dequeue() { + q := NewQueue[int](1, 2, 3) + item, _ := q.Dequeue() + fmt.Println(item) + // Output: 1 +} + +func ExampleQueue_Dequeue_empty() { + q := NewQueue[int]() + _, err := q.Dequeue() + fmt.Println(err) + // Output: queue is empty +} + +func ExampleQueue_Enqueue() { + q := NewQueue[int](1, 2, 3) + _ = q.Enqueue(4) + _ = q.Enqueue(5) + for !q.IsEmpty() { + item, _ := q.Dequeue() + fmt.Println(item) + } + // Output: + // 1 + // 2 + // 3 + // 4 + // 5 +} + +func ExampleQueue_IsEmpty() { + q := NewQueue[int](1, 2, 3) + fmt.Println(q.IsEmpty()) + _, _ = q.Dequeue() + fmt.Println(q.IsEmpty()) + // Output: + // false + // false +} + +func ExampleQueue_Len() { + q := NewQueue[int](1, 2, 3) + fmt.Println(q.Len()) + _, _ = q.Dequeue() + fmt.Println(q.Len()) + // Output: + // 3 + // 2 +} + +func ExampleQueue_Peek() { + q := NewQueue[int](1, 2, 3) + item, _ := q.Peek() + fmt.Println(item) + // Output: 1 +} diff --git a/list.go b/list.go index 4553559..952c1e8 100644 --- a/list.go +++ b/list.go @@ -32,3 +32,4 @@ type List[T comparable] interface { // RemoveAt removes the item at the specified index from the list. If the index is out of range, an error is returned. RemoveAt(int) error } + diff --git a/queue.go b/queue.go new file mode 100644 index 0000000..769716f --- /dev/null +++ b/queue.go @@ -0,0 +1,19 @@ +package collections + +// Queue is a first-in-first-out (FIFO) data structure +type Queue[T any] interface { + // Dequeue removes an element from the queue. If the queue is empty, an error is returned + Dequeue() (T, error) + + // Enqueue adds an element to the queue. If the item cannot be added, an error is returned + Enqueue(T) error + + // IsEmpty returns true if the queue is empty + IsEmpty() bool + + // Peek returns the element at the front of the queue without removing it. If the queue is empty, an error is returned + Peek() (T, error) + + // Len returns the number of elements in the queue + Len() int +} diff --git a/unsafe/queue.go b/unsafe/queue.go new file mode 100644 index 0000000..e7e9c81 --- /dev/null +++ b/unsafe/queue.go @@ -0,0 +1,58 @@ +package unsafe + +import "errors" + +var ( + // ErrQueueEmpty is returned when the queue is empty + ErrQueueEmpty = errors.New("queue is empty") +) + +// Queue is a first-in-first-out (FIFO) data structure that is unsafe for concurrent use +type Queue[T any] struct { + items []T +} + +// NewQueue creates a new Queue +func NewQueue[T any](items ...T) *Queue[T] { + return &Queue[T]{ + items: items, + } +} + +// Dequeue removes an element from the queue. If the queue is empty, an error is returned +func (q *Queue[T]) Dequeue() (T, error) { + var zero T + if len(q.items) == 0 { + return zero, ErrQueueEmpty + } + + item := q.items[0] + q.items = q.items[1:] + return item, nil +} + +// Enqueue adds an element to the queue +func (q *Queue[T]) Enqueue(item T) error { + q.items = append(q.items, item) + return nil +} + +// IsEmpty returns true if the queue is empty +func (q *Queue[T]) IsEmpty() bool { + return len(q.items) == 0 +} + +// Peek returns the element at the front of the queue without removing it. If the queue is empty, an error is returned +func (q *Queue[T]) Peek() (T, error) { + var zero T + if len(q.items) == 0 { + return zero, ErrQueueEmpty + } + + return q.items[0], nil +} + +// Len returns the number of elements in the queue +func (q *Queue[T]) Len() int { + return len(q.items) +} diff --git a/unsafe/queue_test.go b/unsafe/queue_test.go new file mode 100644 index 0000000..975631f --- /dev/null +++ b/unsafe/queue_test.go @@ -0,0 +1,76 @@ +package unsafe + +import "fmt" + +func ExampleNewQueue() { + q := NewQueue[int](1, 2, 3) + _ = q.Enqueue(4) + _ = q.Enqueue(5) + for !q.IsEmpty() { + item, _ := q.Dequeue() + fmt.Println(item) + } + // Output: + // 1 + // 2 + // 3 + // 4 + // 5 +} + +func ExampleQueue_Dequeue() { + q := NewQueue[int](1, 2, 3) + item, _ := q.Dequeue() + fmt.Println(item) + // Output: 1 +} + +func ExampleQueue_Dequeue_empty() { + q := NewQueue[int]() + _, err := q.Dequeue() + fmt.Println(err) + // Output: queue is empty +} + +func ExampleQueue_Enqueue() { + q := NewQueue[int](1, 2, 3) + _ = q.Enqueue(4) + _ = q.Enqueue(5) + for !q.IsEmpty() { + item, _ := q.Dequeue() + fmt.Println(item) + } + // Output: + // 1 + // 2 + // 3 + // 4 + // 5 +} + +func ExampleQueue_IsEmpty() { + q := NewQueue[int](1, 2, 3) + fmt.Println(q.IsEmpty()) + _, _ = q.Dequeue() + fmt.Println(q.IsEmpty()) + // Output: + // false + // false +} + +func ExampleQueue_Len() { + q := NewQueue[int](1, 2, 3) + fmt.Println(q.Len()) + _, _ = q.Dequeue() + fmt.Println(q.Len()) + // Output: + // 3 + // 2 +} + +func ExampleQueue_Peek() { + q := NewQueue[int](1, 2, 3) + item, _ := q.Peek() + fmt.Println(item) + // Output: 1 +}