Skip to content

Commit 4f88325

Browse files
Merge pull request #6 from cloudnative0x0/linked-list
Linked list: new structure added.
2 parents e278fbc + f8cd3ee commit 4f88325

3 files changed

Lines changed: 611 additions & 0 deletions

File tree

linked_list/README.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Linked List
2+
3+
<p style="text-align: left">
4+
<a href="#русский">Русский</a> ・ <a href="#english">English</a>
5+
</p>
6+
7+
---
8+
9+
## Русский
10+
11+
Связный список — линейная структура данных, в которой элементы (узлы) хранятся не в непрерывном блоке памяти, а по отдельности и связаны между собой через указатели. Каждый узел знает только адрес следующего узла — этим список отличается от массива или среза, где все элементы лежат подряд.
12+
13+
Реализация в этом пакете — односвязный список: переход возможен только в одну сторону, от начала к концу.
14+
15+
### Внутреннее устройство
16+
17+
Список хранит три поля: `head`, `tail` и `size`.
18+
19+
- `head` — указатель на первый узел списка.
20+
- `tail` — указатель на последний узел списка. Он нужен, чтобы добавлять элемент в конец за O(1), не пробегая весь список каждый раз.
21+
- `size` — текущее количество элементов.
22+
23+
Каждый узел (`Node[T]`) состоит из значения `Value` и указателя `Next` на следующий узел. У последнего узла `Next` равен `nil` — это признак конца списка.
24+
25+
Если список пуст, оба указателя, `head` и `tail`, равны `nil`.
26+
27+
### Добавление и удаление
28+
29+
- `Prepend` создаёт новый узел и ставит его перед текущим `head`, а сам становится новым `head`. Если список был пуст, этот же узел становится и `tail`.
30+
- `Append` создаёт узел и подвешивает его к `tail.Next`, после чего `tail` сдвигается на него. Если список был пуст, новый узел становится и `head`, и `tail`.
31+
- `InsertAt` вставляет значение на произвольную позицию. Для позиций `0` и `size` используются `Prepend` и `Append`, для остальных случаев список проходится до узла перед нужной позицией, и указатели переставляются.
32+
- `RemoveFirst` возвращает значение из `head` и сдвигает `head` на следующий узел. Если после этого список опустел, `tail` тоже обнуляется.
33+
- `RemoveLast` вынужден пройти список от начала до предпоследнего узла, поскольку у узлов нет указателя назад, на предыдущий элемент. Из-за этого удаление последнего элемента стоит O(n), в отличие от добавления.
34+
- `Remove` ищет первый узел с заданным значением и вырезает его из цепочки, переставляя `Next` у предыдущего узла.
35+
36+
### Поиск и обход
37+
38+
`Search` и `Get` проходят список последовательно от `head`, пока не найдут нужный элемент или не дойдут до конца — прямого доступа по индексу, как в массиве, у связного списка нет.
39+
40+
`Traverse` принимает функцию-колбэк и вызывает её для значения каждого узла по порядку.
41+
42+
`Reverse` разворачивает список на месте: проходит по узлам, у каждого перекладывает `Next` так, чтобы он указывал на предыдущий узел, и в конце меняет местами `head` и `tail`.
43+
44+
`ToSlice` собирает значения всех узлов в обычный срез — удобно для отладки или передачи данных туда, где нужен произвольный доступ по индексу.
45+
46+
### Использование
47+
48+
```go
49+
list := New[int]()
50+
list.Append(1)
51+
list.Append(2)
52+
list.Prepend(0)
53+
54+
val, _ := list.Get(1) // val = 1
55+
list.Reverse()
56+
slice := list.ToSlice() // [2, 1, 0]
57+
```
58+
59+
### Операции
60+
61+
| Операция | Сложность | Описание |
62+
|---|---|---|
63+
| `Prepend(x)` | O(1) | добавить элемент в начало списка |
64+
| `Append(x)` | O(1) | добавить элемент в конец списка |
65+
| `InsertAt(pos, x)` | O(n) | вставить элемент на произвольную позицию |
66+
| `RemoveFirst()` | O(1) | удалить и вернуть первый элемент |
67+
| `RemoveLast()` | O(n) | удалить и вернуть последний элемент |
68+
| `Remove(x)` | O(n) | удалить первый узел с заданным значением |
69+
| `Search(x)` | O(n) | проверить, есть ли значение в списке |
70+
| `Get(pos)` | O(n) | получить значение по позиции |
71+
| `Traverse(fn)` | O(n) | пройти список и вызвать функцию для каждого значения |
72+
| `Reverse()` | O(n) | развернуть список на месте |
73+
| `ToSlice()` | O(n) | получить список значений в виде среза |
74+
| `Len()` | O(1) | текущее количество элементов |
75+
| `IsEmpty()` | O(1) | проверка, пуст ли список |
76+
77+
### Сборка и тестирование
78+
79+
```bash
80+
go test -v ./...
81+
```
82+
83+
---
84+
85+
## English
86+
87+
A linked list is a linear data structure where elements (nodes) are not stored in one contiguous block of memory, but individually, connected to each other through pointers. Each node only knows the address of the next node — this is what sets it apart from an array or a slice, where all elements sit next to each other.
88+
89+
The implementation in this package is a singly linked list: traversal is only possible in one direction, from the start toward the end.
90+
91+
### Internal layout
92+
93+
The list keeps three fields: `head`, `tail`, and `size`.
94+
95+
- `head` points to the first node of the list.
96+
- `tail` points to the last node of the list. It exists so that adding an element to the end costs O(1) instead of walking the whole list every time.
97+
- `size` holds the current number of elements.
98+
99+
Each node (`Node[T]`) consists of a value `Value` and a pointer `Next` to the following node. The last node has `Next` set to `nil` — that is the marker for the end of the list.
100+
101+
When the list is empty, both `head` and `tail` are `nil`.
102+
103+
### Insertion and removal
104+
105+
- `Prepend` creates a new node and places it before the current `head`, becoming the new `head`. If the list was empty, this same node also becomes `tail`.
106+
- `Append` creates a node and attaches it to `tail.Next`, then moves `tail` to it. If the list was empty, the new node becomes both `head` and `tail`.
107+
- `InsertAt` inserts a value at an arbitrary position. Positions `0` and `size` fall back to `Prepend` and `Append`; for anything in between, the list is walked up to the node just before the target position, and the pointers are relinked.
108+
- `RemoveFirst` returns the value at `head` and moves `head` to the next node. If the list becomes empty as a result, `tail` is cleared too.
109+
- `RemoveLast` has to walk the list from the beginning up to the second-to-last node, since nodes have no pointer back to their predecessor. Because of this, removing the last element costs O(n), unlike appending.
110+
- `Remove` looks for the first node holding the given value and cuts it out of the chain by relinking the previous node's `Next`.
111+
112+
### Search and traversal
113+
114+
`Search` and `Get` walk the list sequentially from `head` until they find the target element or reach the end — a linked list has no direct index-based access the way an array does.
115+
116+
`Traverse` takes a callback function and calls it with the value of every node in order.
117+
118+
`Reverse` reverses the list in place: it walks the nodes, flips each one's `Next` to point at the previous node, and finally swaps `head` and `tail`.
119+
120+
`ToSlice` collects the values of all nodes into a plain slice — useful for debugging or for passing the data to something that needs index-based access.
121+
122+
### Usage
123+
124+
```go
125+
list := New[int]()
126+
list.Append(1)
127+
list.Append(2)
128+
list.Prepend(0)
129+
130+
val, _ := list.Get(1) // val = 1
131+
list.Reverse()
132+
slice := list.ToSlice() // [2, 1, 0]
133+
```
134+
135+
### Operations
136+
137+
| Operation | Complexity | Description |
138+
|---|---|---|
139+
| `Prepend(x)` | O(1) | add an element to the front of the list |
140+
| `Append(x)` | O(1) | add an element to the back of the list |
141+
| `InsertAt(pos, x)` | O(n) | insert an element at an arbitrary position |
142+
| `RemoveFirst()` | O(1) | remove and return the first element |
143+
| `RemoveLast()` | O(n) | remove and return the last element |
144+
| `Remove(x)` | O(n) | remove the first node holding the given value |
145+
| `Search(x)` | O(n) | check whether a value exists in the list |
146+
| `Get(pos)` | O(n) | get the value at a given position |
147+
| `Traverse(fn)` | O(n) | walk the list and call a function for each value |
148+
| `Reverse()` | O(n) | reverse the list in place |
149+
| `ToSlice()` | O(n) | get the list's values as a slice |
150+
| `Len()` | O(1) | current number of elements |
151+
| `IsEmpty()` | O(1) | check whether the list is empty |
152+
153+
### Build and test
154+
155+
```bash
156+
go test -v ./...
157+
```
158+
159+
---
160+
161+
<br>
162+
163+
> Связный список не хранит карту всего пути — только адрес следующего шага. Чтобы дойти до конца, нужно пройти через каждое звено цепи, ни одно не пропустить.
164+
>
165+
> *A linked list keeps no map of the whole path — only the address of the next step. To reach the end, you have to pass through every link in the chain, skipping none.*

linked_list/linked_list.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package linked_list
2+
3+
import (
4+
"errors"
5+
"strings"
6+
)
7+
8+
var (
9+
ErrEmptyList = errors.New("list is empty")
10+
ErrOutOfBounds = errors.New("index out of bounds")
11+
ErrNotFound = errors.New("value not found")
12+
)
13+
14+
type Node[T any] struct {
15+
Value T
16+
Next *Node[T]
17+
}
18+
19+
type LinkedList[T comparable] struct {
20+
head *Node[T]
21+
tail *Node[T]
22+
size int
23+
}
24+
25+
func New[T comparable]() *LinkedList[T] {
26+
return &LinkedList[T]{}
27+
}
28+
29+
func (ll *LinkedList[T]) Len() int { return ll.size }
30+
31+
func (ll *LinkedList[T]) IsEmpty() bool { return ll.size == 0 }
32+
33+
func (ll *LinkedList[T]) Prepend(value T) {
34+
node := &Node[T]{Value: value, Next: ll.head}
35+
ll.head = node
36+
if ll.tail == nil {
37+
ll.tail = node
38+
}
39+
40+
ll.size++
41+
}
42+
43+
func (ll *LinkedList[T]) Append(value T) {
44+
node := &Node[T]{Value: value}
45+
if ll.tail == nil {
46+
ll.head = node
47+
ll.tail = node
48+
} else {
49+
ll.tail.Next = node
50+
ll.tail = node
51+
}
52+
53+
ll.size++
54+
}
55+
56+
func (ll *LinkedList[T]) InsertAt(position int, value T) error {
57+
if position < 0 || position > ll.size {
58+
return ErrOutOfBounds
59+
}
60+
if position == 0 {
61+
ll.Prepend(value)
62+
return nil
63+
}
64+
if position == ll.size {
65+
ll.Append(value)
66+
return nil
67+
}
68+
69+
prev := ll.head
70+
for i := 0; i < position-1; i++ {
71+
prev = prev.Next
72+
}
73+
74+
node := &Node[T]{Value: value, Next: prev.Next}
75+
prev.Next = node
76+
ll.size++
77+
78+
return nil
79+
}
80+
81+
func (ll *LinkedList[T]) RemoveFirst() (T, error) {
82+
var zero T
83+
if ll.head == nil {
84+
return zero, ErrEmptyList
85+
}
86+
87+
val := ll.head.Value
88+
89+
ll.head = ll.head.Next
90+
if ll.head == nil {
91+
ll.tail = nil
92+
}
93+
94+
ll.size--
95+
96+
return val, nil
97+
}
98+
99+
func (ll *LinkedList[T]) RemoveLast() (T, error) {
100+
var zero T
101+
if ll.head == nil {
102+
return zero, ErrEmptyList
103+
}
104+
if ll.head.Next == nil {
105+
val := ll.head.Value
106+
ll.head = nil
107+
ll.tail = nil
108+
ll.size--
109+
return val, nil
110+
}
111+
112+
prev := ll.head
113+
for prev.Next.Next != nil {
114+
prev = prev.Next
115+
}
116+
117+
val := prev.Next.Value
118+
prev.Next = nil
119+
ll.tail = prev
120+
ll.size--
121+
122+
return val, nil
123+
}
124+
125+
func (ll *LinkedList[T]) Remove(value T) error {
126+
if ll.head == nil {
127+
return ErrEmptyList
128+
}
129+
if ll.head.Value == value {
130+
ll.head = ll.head.Next
131+
132+
if ll.head == nil {
133+
ll.tail = nil
134+
}
135+
ll.size--
136+
137+
return nil
138+
}
139+
140+
prev := ll.head
141+
for prev.Next != nil {
142+
if prev.Next.Value == value {
143+
if prev.Next == ll.tail {
144+
ll.tail = prev
145+
}
146+
147+
prev.Next = prev.Next.Next
148+
ll.size--
149+
150+
return nil
151+
}
152+
153+
prev = prev.Next
154+
}
155+
156+
return ErrNotFound
157+
}
158+
159+
func (ll *LinkedList[T]) Search(value T) bool {
160+
for cur := ll.head; cur != nil; cur = cur.Next {
161+
if cur.Value == value {
162+
return true
163+
}
164+
}
165+
166+
return false
167+
}
168+
169+
func (ll *LinkedList[T]) Get(position int) (T, error) {
170+
var zero T
171+
if position < 0 || position >= ll.size {
172+
return zero, ErrOutOfBounds
173+
}
174+
cur := ll.head
175+
for i := 0; i < position; i++ {
176+
cur = cur.Next
177+
}
178+
179+
return cur.Value, nil
180+
}
181+
182+
func (ll *LinkedList[T]) Traverse(callback func(value T)) {
183+
for cur := ll.head; cur != nil; cur = cur.Next {
184+
callback(cur.Value)
185+
}
186+
}
187+
188+
func (ll *LinkedList[T]) Reverse() {
189+
var prev *Node[T]
190+
191+
cur := ll.head
192+
ll.tail = ll.head
193+
194+
for cur != nil {
195+
next := cur.Next
196+
cur.Next = prev
197+
prev = cur
198+
cur = next
199+
}
200+
201+
ll.head = prev
202+
}
203+
204+
func (ll *LinkedList[T]) ToSlice() []T {
205+
slice := make([]T, 0, ll.size)
206+
for cur := ll.head; cur != nil; cur = cur.Next {
207+
slice = append(slice, cur.Value)
208+
}
209+
210+
return slice
211+
}
212+
213+
func (ll *LinkedList[T]) String() string {
214+
var b strings.Builder
215+
b.WriteString("[")
216+
for cur := ll.head; cur != nil; cur = cur.Next {
217+
218+
if cur.Next != nil {
219+
b.WriteString(" -> ")
220+
}
221+
}
222+
b.WriteString("]")
223+
224+
return b.String()
225+
}

0 commit comments

Comments
 (0)