Skip to content

Commit 9eca135

Browse files
Merge pull request #1 from cloudnative0x0/stack
stack implemented, doc and test created.
2 parents c57d4bd + 816f18f commit 9eca135

4 files changed

Lines changed: 317 additions & 7 deletions

File tree

main.go

Lines changed: 0 additions & 7 deletions
This file was deleted.

stack/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Stack
2+
3+
<p style="text-align: left">
4+
<a href="#русский">Русский</a> ・ <a href="#english">English</a>
5+
</p>
6+
7+
---
8+
9+
## Русский
10+
11+
Стек — линейная структура данных, работающая по принципу **LIFO** (Last In, First Out): последний вошедший элемент выходит первым.
12+
13+
Пример: если добавить элементы в порядке `1, 2, 3`, то `3` окажется наверху и будет извлечён первым, следом `2`, затем `1`. Порядок входа и выхода полностью противоположный.
14+
15+
### Внутреннее устройство
16+
17+
Реализация хранит срез `arr` и число `top` — индекс последнего добавленного элемента. Активная часть стека — это `arr[1..top]`, где:
18+
19+
- `arr[1]` — нижний, самый первый добавленный элемент
20+
- `arr[top]` — вершина стека, элемент, добавленный последним
21+
22+
Если `top == 0`, стек пуст. Если `top == n` (максимальная вместимость), попытка добавить новый элемент приводит к переполнению.
23+
24+
Срез создаётся размером `n+1`, а не `n` — нулевая ячейка `arr[0]` физически существует, но не используется, поскольку индексация ведётся с 1, как в оригинальном псевдокоде.
25+
26+
### Использование
27+
28+
```go
29+
s := NewStack(5)
30+
31+
s.Push(1)
32+
s.Push(2)
33+
s.Push(3)
34+
35+
val, err := s.Pop() // val = 3, err = nil
36+
```
37+
38+
### Операции
39+
40+
| Операция | Сложность | Описание |
41+
|---|---|---|
42+
| `Push(x)` | O(1) | добавить элемент на вершину |
43+
| `Pop()` | O(1) | снять и вернуть элемент с вершины |
44+
| `IsEmpty()` | O(1) | проверка на пустоту |
45+
| `IsFull()` | O(1) | проверка на переполнение |
46+
| `Size()` | O(1) | текущее количество элементов |
47+
48+
### Сборка и тестирование
49+
50+
```bash
51+
go test -v ./...
52+
```
53+
54+
Корректность проверяется stress-тестом — реализация сравнивается с обычным срезом, работающим как стек, на большом числе случайных последовательностей операций.
55+
56+
---
57+
58+
## English
59+
60+
A stack is a linear data structure that follows the **LIFO** principle (Last In, First Out): the element inserted last is the first one to leave.
61+
62+
Example: inserting elements in the order `1, 2, 3` places `3` on top, so it comes out first, followed by `2`, then `1`. The exit order is the exact reverse of the entry order.
63+
64+
### Internal layout
65+
66+
The implementation keeps a slice `arr` and a number `top` — the index of the most recently inserted element. The active part of the stack is `arr[1..top]`, where:
67+
68+
- `arr[1]` is the bottom, the very first element inserted
69+
- `arr[top]` is the top of the stack, the most recently inserted element
70+
71+
If `top == 0`, the stack is empty. If `top == n` (the fixed capacity), inserting another element causes an overflow.
72+
73+
The slice is allocated with size `n+1`, not `n` — the zero cell `arr[0]` exists but is never used, since indexing starts at 1, matching the original pseudocode.
74+
75+
### Usage
76+
77+
```go
78+
s := NewStack(5)
79+
80+
s.Push(1)
81+
s.Push(2)
82+
s.Push(3)
83+
84+
val, err := s.Pop() // val = 3, err = nil
85+
```
86+
87+
### Operations
88+
89+
| Operation | Complexity | Description |
90+
|---|---|---|
91+
| `Push(x)` | O(1) | insert an element on top |
92+
| `Pop()` | O(1) | remove and return the top element |
93+
| `IsEmpty()` | O(1) | check whether the stack is empty |
94+
| `IsFull()` | O(1) | check whether the stack is at capacity |
95+
| `Size()` | O(1) | current number of elements |
96+
97+
### Build and test
98+
99+
```bash
100+
go test -v ./...
101+
```
102+
103+
Correctness is checked with a stress test — the implementation is compared against a plain slice used as a stack, over a large number of randomized operation sequences.
104+
105+
---
106+
107+
<br>
108+
109+
> Стек не помнит, кто был первым. Он помнит только, кто был последним — и отдаёт именно его. Так устроена память толпы, поколений, целых цивилизаций: наверху всегда то, что случилось недавно, а самое первое, изначальное, погребено глубже всего и достаётся последним, если вообще достаётся.
110+
>
111+
> *A stack has no memory of who came first. It only remembers who came last — and gives that one back. This is how the memory of a crowd works, of generations, of entire civilizations: what happened recently always sits on top, while the very first, the original, lies buried deepest and is reached last, if it is reached at all.*

stack/stack.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package stack
2+
3+
import "fmt"
4+
5+
type Stack struct {
6+
arr []int
7+
top int
8+
n int
9+
}
10+
11+
func NewStack(n int) *Stack {
12+
return &Stack{
13+
arr: make([]int, n+1),
14+
top: 0,
15+
n: n,
16+
}
17+
}
18+
19+
func (s *Stack) Pop() (int, error) {
20+
if s.top == 0 {
21+
return 0, fmt.Errorf("stack underflow")
22+
}
23+
24+
s.top = s.top - 1
25+
26+
return s.arr[s.top+1], nil
27+
}
28+
29+
func (s *Stack) Push(x int) error {
30+
if s.IsFull() {
31+
return fmt.Errorf("stack overflow")
32+
}
33+
34+
s.top = s.top + 1
35+
s.arr[s.top] = x
36+
37+
return nil
38+
}
39+
40+
func (s *Stack) IsEmpty() bool {
41+
return s.top == 0
42+
}
43+
44+
func (s *Stack) IsFull() bool {
45+
return s.top == s.n
46+
}
47+
48+
func (s *Stack) Size() int {
49+
return s.top
50+
}

stack/stack_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package stack
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
)
7+
8+
func TestPushPop(t *testing.T) {
9+
s := NewStack(5)
10+
11+
if err := s.Push(10); err != nil {
12+
t.Fatalf("unexpected error on push: %v", err)
13+
}
14+
15+
val, err := s.Pop()
16+
if err != nil {
17+
t.Fatalf("unexpected error on pop: %v", err)
18+
}
19+
if val != 10 {
20+
t.Errorf("expected 10, got %d", val)
21+
}
22+
}
23+
24+
func TestLIFOOrder(t *testing.T) {
25+
s := NewStack(3)
26+
27+
if err := s.Push(1); err != nil {
28+
t.Fatalf("unexpected error on push: %v", err)
29+
}
30+
if err := s.Push(2); err != nil {
31+
t.Fatalf("unexpected error on push: %v", err)
32+
}
33+
if err := s.Push(3); err != nil {
34+
t.Fatalf("unexpected error on push: %v", err)
35+
}
36+
37+
expected := []int{3, 2, 1}
38+
39+
for i, want := range expected {
40+
got, err := s.Pop()
41+
if err != nil {
42+
t.Fatalf("unexpected error on pop #%d: %v", i, err)
43+
}
44+
if got != want {
45+
t.Errorf("pop #%d: expected %d, got %d", i, want, got)
46+
}
47+
}
48+
}
49+
50+
func TestEmptyStack(t *testing.T) {
51+
s := NewStack(3)
52+
53+
if !s.IsEmpty() {
54+
t.Error("new stack should be empty")
55+
}
56+
57+
_, err := s.Pop()
58+
if err == nil {
59+
t.Error("expected underflow error, got nil")
60+
}
61+
}
62+
63+
func TestFullStack(t *testing.T) {
64+
s := NewStack(2)
65+
66+
if err := s.Push(1); err != nil {
67+
t.Fatalf("unexpected error on push: %v", err)
68+
}
69+
if err := s.Push(2); err != nil {
70+
t.Fatalf("unexpected error on push: %v", err)
71+
}
72+
73+
if !s.IsFull() {
74+
t.Error("stack should be full")
75+
}
76+
77+
err := s.Push(3)
78+
if err == nil {
79+
t.Error("expected overflow error, got nil")
80+
}
81+
}
82+
83+
func TestSize(t *testing.T) {
84+
s := NewStack(5)
85+
86+
if s.Size() != 0 {
87+
t.Errorf("expected size 0, got %d", s.Size())
88+
}
89+
90+
if err := s.Push(1); err != nil {
91+
t.Fatalf("unexpected error on push: %v", err)
92+
}
93+
if err := s.Push(2); err != nil {
94+
t.Fatalf("unexpected error on push: %v", err)
95+
}
96+
97+
if s.Size() != 2 {
98+
t.Errorf("expected size 2, got %d", s.Size())
99+
}
100+
101+
if _, err := s.Pop(); err != nil {
102+
t.Fatalf("unexpected error on pop: %v", err)
103+
}
104+
105+
if s.Size() != 1 {
106+
t.Errorf("expected size 1, got %d", s.Size())
107+
}
108+
}
109+
110+
func TestStress(t *testing.T) {
111+
rng := rand.New(rand.NewSource(42))
112+
113+
const iterations = 1000
114+
const opsPerIteration = 50
115+
const capacity = 30
116+
117+
for iter := 0; iter < iterations; iter++ {
118+
mine := NewStack(capacity)
119+
var ref []int
120+
121+
for op := 0; op < opsPerIteration; op++ {
122+
switch rng.Intn(3) {
123+
case 0: // push
124+
if !mine.IsFull() {
125+
val := rng.Intn(1000)
126+
if err := mine.Push(val); err != nil {
127+
t.Fatalf("iter %d: unexpected push error: %v", iter, err)
128+
}
129+
ref = append(ref, val)
130+
}
131+
132+
case 1: // pop
133+
if len(ref) > 0 {
134+
want := ref[len(ref)-1]
135+
ref = ref[:len(ref)-1]
136+
137+
got, err := mine.Pop()
138+
if err != nil {
139+
t.Fatalf("iter %d: unexpected pop error: %v", iter, err)
140+
}
141+
if got != want {
142+
t.Fatalf("iter %d: mismatch on pop: expected %d, got %d", iter, want, got)
143+
}
144+
}
145+
146+
case 2:
147+
if mine.Size() != len(ref) {
148+
t.Fatalf("iter %d: size mismatch: expected %d, got %d", iter, len(ref), mine.Size())
149+
}
150+
if mine.IsEmpty() != (len(ref) == 0) {
151+
t.Fatalf("iter %d: isEmpty mismatch", iter)
152+
}
153+
}
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)