Skip to content

Commit 4aa5dd6

Browse files
Merge pull request #2 from cloudnative0x0/queue
Queue: new data structure added.
2 parents 9eca135 + 7225998 commit 4aa5dd6

6 files changed

Lines changed: 407 additions & 7 deletions

File tree

.github/workflows/pull.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
name: CI Workflow
1+
name: CI Workflow (PR)
22

33
on:
44
pull_request:
55
branches:
66
- main
77

88
jobs:
9-
lint:
9+
lint-and-test:
1010
runs-on: ubuntu-latest
1111

1212
steps:
@@ -24,3 +24,7 @@ jobs:
2424
version: latest
2525
args: |
2626
-v ./...
27+
28+
- name: Run tests
29+
run: go test -race -v ./...
30+

.github/workflows/push.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
name: CI Workflow
1+
name: CI Workflow (Push)
22

33
on:
44
push:
55
branches:
66
- main
77

88
jobs:
9-
lint:
9+
lint-and-test:
1010
runs-on: ubuntu-latest
1111

1212
steps:
@@ -24,3 +24,7 @@ jobs:
2424
version: latest
2525
args: |
2626
-v ./...
27+
28+
- name: Run tests
29+
run: go test -race -v ./...
30+

queue/README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Queue
2+
3+
<p style="text-align: left">
4+
<a href="#русский">Русский</a> ・ <a href="#english">English</a>
5+
</p>
6+
7+
---
8+
9+
## Русский
10+
11+
Очередь — линейная структура данных, работающая по принципу **FIFO** (First In, First Out): элемент, добавленный первым, извлекается первым. Это полная противоположность стеку.
12+
13+
Пример: если добавить элементы в порядке `1, 2, 3`, то первым выйдет `1`, затем `2`, затем `3`. Порядок выхода в точности повторяет порядок входа.
14+
15+
### Внутреннее устройство
16+
17+
Реализация использует кольцевой буфер на основе среза `arr`, два индекса — `head` и `tail`, а также счётчик `count`.
18+
19+
- `head` указывает на ячейку, из которой будет читать следующая операция `Dequeue` или `Peek`.
20+
- `tail` указывает на ячейку, в которую запишет следующая операция `Enqueue`.
21+
- `count` хранит текущее число элементов в очереди. Оно необходимо, чтобы различать состояния «пуста» и «полна», так как при `head == tail` буфер может быть и пустым, и полностью заполненным — только счётчик даёт ответ.
22+
23+
При добавлении элемента (`Enqueue`) значение записывается в `arr[tail]`, после чего `tail` сдвигается циклически вперёд по формуле `(tail + 1) % len(arr)`, а `count` увеличивается на единицу.
24+
25+
При извлечении (`Dequeue`) читается `arr[head]`, затем `head` сдвигается вперёд так же циклически, а `count` уменьшается.
26+
27+
Фиксированная ёмкость очереди задаётся при создании и не меняется. Попытка добавить элемент в заполненную очередь приводит к ошибке, как и попытка извлечь из пустой.
28+
29+
Дополнительно доступны методы `Size()` (возвращает `count`) и `Cap()` (возвращает `len(arr)`), позволяющие узнать текущую заполненность и предельную вместимость.
30+
31+
### Использование
32+
33+
```go
34+
q, err := NewQueue[int](3)
35+
if err != nil {
36+
// обработать неверную ёмкость
37+
}
38+
q.Enqueue(10)
39+
q.Enqueue(20)
40+
val, _ := q.Dequeue() // val = 10
41+
```
42+
43+
### Операции
44+
45+
| Операция | Сложность | Описание |
46+
|---|---|---|
47+
| `Enqueue(x)` | O(1) | добавить элемент в конец очереди |
48+
| `Dequeue()` | O(1) | извлечь элемент из начала очереди |
49+
| `Peek()` | O(1) | посмотреть элемент в начале, не удаляя |
50+
| `IsEmpty()` | O(1) | проверка, пуста ли очередь |
51+
| `IsFull()` | O(1) | проверка, заполнена ли очередь |
52+
| `Size()` | O(1) | текущее количество элементов |
53+
| `Cap()` | O(1) | максимальная ёмкость |
54+
55+
### Сборка и тестирование
56+
57+
```bash
58+
go test -v ./...
59+
```
60+
61+
Корректность проверяется с помощью набора модульных тестов, покрывающих граничные случаи, циклическое поведение индексов и стресс-теста, который сравнивает поведение очереди с эталонной реализацией на срезе.
62+
63+
---
64+
65+
## English
66+
67+
A queue is a linear data structure that follows the **FIFO** principle (First In, First Out): the element inserted first is the first one to be removed. It is the exact opposite of a stack.
68+
69+
Example: inserting elements in the order `1, 2, 3` will retrieve `1` first, then `2`, then `3`. The order of removal precisely matches the order of insertion.
70+
71+
### Internal layout
72+
73+
The implementation uses a circular buffer built on a slice `arr`, two indices `head` and `tail`, and a counter `count`.
74+
75+
- `head` points to the cell from which the next `Dequeue` or `Peek` operation will read.
76+
- `tail` points to the cell where the next `Enqueue` operation will write.
77+
- `count` holds the current number of elements. It is essential to tell apart the empty and full states, because when `head == tail` the buffer could be either empty or completely full — only the counter provides the answer.
78+
79+
When an element is added (`Enqueue`), the value is stored in `arr[tail]`, then `tail` advances circularly via `(tail + 1) % len(arr)`, and `count` increases by one.
80+
81+
When an element is removed (`Dequeue`), the value at `arr[head]` is read, then `head` advances circularly in the same way, and `count` decreases.
82+
83+
The fixed capacity of the queue is set at creation and never changes. Inserting into a full queue or removing from an empty one both result in errors.
84+
85+
Additionally, `Size()` (returns `count`) and `Cap()` (returns `len(arr)`) are provided to inspect how many elements are currently stored and what the maximum capacity is.
86+
87+
### Usage
88+
89+
```go
90+
q, err := NewQueue[int](3)
91+
if err != nil {
92+
// handle invalid capacity
93+
}
94+
q.Enqueue(10)
95+
q.Enqueue(20)
96+
val, _ := q.Dequeue() // val = 10
97+
```
98+
99+
### Operations
100+
101+
| Operation | Complexity | Description |
102+
|---|---|---|
103+
| `Enqueue(x)` | O(1) | add an element to the back of the queue |
104+
| `Dequeue()` | O(1) | remove and return the front element |
105+
| `Peek()` | O(1) | return the front element without removing |
106+
| `IsEmpty()` | O(1) | check whether the queue is empty |
107+
| `IsFull()` | O(1) | check whether the queue is at capacity |
108+
| `Size()` | O(1) | current number of elements |
109+
| `Cap()` | O(1) | maximum capacity |
110+
111+
### Build and test
112+
113+
```bash
114+
go test -v ./...
115+
```
116+
117+
Correctness is verified through a suite of unit tests that cover edge cases, circular index wrapping, and a stress test that compares the queue's behavior against a reference slice-based implementation.
118+
119+
---
120+
121+
<br>
122+
123+
> Очередь не смотрит на важность, не слышит просьб пропустить вперёд. Она знает только одно: кто пришёл раньше, тот и уйдёт раньше. В этом её слепая справедливость.
124+
>
125+
> *A queue does not look at importance, nor does it hear pleas to skip ahead. It knows only one thing: whoever came first leaves first. In that lies its blind justice.*

queue/queue.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package queue
2+
3+
import "errors"
4+
5+
var (
6+
ErrEmpty = errors.New("queue is empty")
7+
ErrFull = errors.New("queue is full")
8+
ErrInvalidCapacity = errors.New("capacity must be greater than zero")
9+
)
10+
11+
type Queue[T any] struct {
12+
arr []T
13+
head int
14+
tail int
15+
count int
16+
}
17+
18+
func NewQueue[T any](capacity int) (*Queue[T], error) {
19+
if capacity <= 0 {
20+
return nil, ErrInvalidCapacity
21+
}
22+
return &Queue[T]{
23+
arr: make([]T, capacity),
24+
}, nil
25+
}
26+
27+
func (q *Queue[T]) Enqueue(value T) error {
28+
if q.IsFull() {
29+
return ErrFull
30+
}
31+
32+
q.arr[q.tail] = value
33+
q.tail = (q.tail + 1) % len(q.arr)
34+
q.count++
35+
36+
return nil
37+
}
38+
39+
func (q *Queue[T]) Dequeue() (T, error) {
40+
if q.IsEmpty() {
41+
var zero T
42+
return zero, ErrEmpty
43+
}
44+
45+
oldHead := q.arr[q.head]
46+
q.head = (q.head + 1) % len(q.arr)
47+
q.count--
48+
49+
return oldHead, nil
50+
}
51+
52+
func (q *Queue[T]) Peek() (T, error) {
53+
if q.IsEmpty() {
54+
var zero T
55+
return zero, ErrEmpty
56+
}
57+
58+
return q.arr[q.head], nil
59+
}
60+
61+
func (q *Queue[T]) IsEmpty() bool {
62+
return q.count == 0
63+
}
64+
65+
func (q *Queue[T]) IsFull() bool {
66+
return q.count == len(q.arr)
67+
}

0 commit comments

Comments
 (0)