Skip to content

Commit dda632d

Browse files
committed
feat: add linked list chapter
1 parent 2c4bccb commit dda632d

5 files changed

Lines changed: 338 additions & 7 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ jobs:
2121
test -s ROADMAP.md
2222
test -s docs/SUMMARY.md
2323
test -s docs/01-vector.md
24+
test -s docs/02-linked-list.md
2425
test -s examples/README.md
2526
test -s tests/README.md
2627
test -s benches/README.md
2728
test -s benches/vector_bench.rs
29+
test -s benches/linked_list_bench.rs
2830
test -s diagrams/README.md
2931
test -s diagrams/01-vector.mmd
32+
test -s diagrams/02-linked-list.mmd
3033
test -s assets/README.md
3134
3235
grep -q "Vector" docs/SUMMARY.md

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ un algoritmo sea necesario para explicar la propia estructura.
3131
| # | Capitulo | Modulo | Estado |
3232
|---|----------|--------|--------|
3333
| 01 | Vector | `src/vector.rs` | benchmarked |
34-
| 02 | Linked List | `src/linked_list.rs` | planned |
34+
| 02 | Linked List | `src/linked_list.rs` | benchmarked |
3535
| 03 | Stack | `src/stack.rs` | planned |
3636
| 04 | Queue | `src/queue.rs` | planned |
3737
| 05 | Deque | `src/deque.rs` | planned |

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ El checklist detallado vive en
2020
| # | Capitulo | Estado |
2121
|---|----------|--------|
2222
| 01 | Vector | benchmarked |
23-
| 02 | Linked List | planned |
23+
| 02 | Linked List | benchmarked |
2424
| 03 | Stack | planned |
2525
| 04 | Queue | planned |
2626
| 05 | Deque | planned |

docs/02-linked-list.md

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# Linked List
2+
3+
> **Curso:** rust-data-structures · **Capítulo:** 02 · **Prerequisitos:** Capítulo 01, Vector
4+
> **Código:** [`src/linked_list.rs`](../src/linked_list.rs) · **Video:** pendiente
5+
> **Lección en el sitio:** pendiente
6+
7+
## Introducción
8+
9+
Una lista enlazada representa una secuencia como nodos conectados. Cada nodo
10+
guarda un valor y un enlace al siguiente nodo. A diferencia del vector, sus
11+
elementos no viven necesariamente en memoria contigua: la estructura se recorre
12+
siguiendo enlaces.
13+
14+
Este capítulo enseña listas enlazadas simples en Rust seguro. El objetivo no es
15+
decir que son "mejores" que los vectores, sino entender qué ganan y qué pagan:
16+
inserción barata al frente, recorrido secuencial natural y peor localidad de
17+
memoria.
18+
19+
## Motivación
20+
21+
Imagina una cola de reintentos para trabajos fallidos: procesas el primer trabajo,
22+
quizá agregas otros al final, y rara vez necesitas acceso aleatorio por índice.
23+
Un vector puede resolverlo, pero remover del frente obliga a desplazar elementos.
24+
Una lista enlazada permite tomar el primer nodo cambiando un enlace.
25+
26+
La lista enlazada existe para casos donde la forma natural del problema es
27+
seguir enlaces y mover nodos, no saltar a posiciones arbitrarias. Esa diferencia
28+
es la razón por la que aparece justo después de `Vector`: enseña el contraste
29+
entre memoria contigua y nodos dispersos.
30+
31+
## Teoría
32+
33+
### Historia
34+
35+
Las listas enlazadas son una de las estructuras clásicas de la programación de
36+
sistemas. Aparecen temprano porque permiten construir colecciones dinámicas sin
37+
tener que mover todos los elementos cuando se inserta al frente o cuando se
38+
reconecta una parte de la estructura.
39+
40+
En lenguajes con punteros manuales, una lista enlazada suele enseñarse como
41+
"nodo + puntero". En Rust, el mismo concepto obliga a ser más preciso: ¿quién es
42+
dueño del nodo?, ¿quién destruye la cadena?, ¿puede existir un enlace colgante?
43+
Por eso este capítulo es una buena introducción a ownership aplicado.
44+
45+
### Fundamentos
46+
47+
Nuestra lista usa una representación simple:
48+
49+
```rust
50+
pub struct LinkedList<T> {
51+
head: Option<Box<Node<T>>>,
52+
len: usize,
53+
}
54+
```
55+
56+
Cada `Node<T>` contiene un valor y un `next`. La lista posee el primer nodo. Cada
57+
nodo posee el siguiente. Cuando la lista se destruye, la cadena completa se
58+
destruye por ownership.
59+
60+
Invariantes principales:
61+
62+
- `len` es el número de nodos alcanzables desde `head`.
63+
- Si `len == 0`, `head == None`.
64+
- Si `len > 0`, `head == Some(...)`.
65+
- El último nodo tiene `next == None`.
66+
67+
Esta implementación no guarda un puntero al último nodo. Eso mantiene el diseño
68+
seguro y fácil de enseñar, pero hace que `push_back`, `pop_back` y `back` sean
69+
O(n): hay que recorrer desde `head`.
70+
71+
### Casos de uso
72+
73+
Las listas enlazadas sirven cuando el patrón dominante es secuencial:
74+
75+
- Pilas con `push_front` y `pop_front`.
76+
- Colas simples cuando se acepta recorrer para insertar al final.
77+
- Cadenas de manejadores o pasos de procesamiento.
78+
- Estructuras donde los nodos se reconectan con frecuencia.
79+
- Fundamento conceptual para estructuras más avanzadas como skip lists.
80+
81+
También son valiosas pedagógicamente: muestran ownership, movimiento de valores,
82+
destrucción de nodos y el costo real de perder localidad de memoria.
83+
84+
### Ventajas y limitaciones
85+
86+
Ventajas:
87+
88+
- `push_front` y `pop_front` son O(1).
89+
- No requiere mover todos los elementos al insertar al frente.
90+
- Cada nodo puede tener vida y ownership claros.
91+
- Es una base conceptual para estructuras enlazadas más complejas.
92+
93+
Limitaciones:
94+
95+
- Acceso por índice es O(n) si se implementa.
96+
- `push_back` es O(n) en esta versión sin puntero a cola.
97+
- Peor localidad de caché que un vector.
98+
- Más asignaciones pequeñas.
99+
- Más complejidad conceptual por nodos y enlaces.
100+
101+
### Comparación con alternativas
102+
103+
Un vector suele ser mejor si necesitas recorrer muchos elementos, acceder por
104+
índice o aprovechar caché. Una lista enlazada gana cuando la operación central
105+
está cerca de la cabeza y no necesitas acceso aleatorio.
106+
107+
Un deque es mejor si necesitas operaciones eficientes en ambos extremos. Una
108+
lista doblemente enlazada puede remover desde ambos lados con más flexibilidad,
109+
pero introduce enlaces hacia atrás y más invariantes. Una lista intrusiva puede
110+
evitar asignaciones por nodo, pero ya pertenece a programación de bajo nivel y
111+
normalmente requiere `unsafe`.
112+
113+
## Diagramas
114+
115+
El diagrama principal vive en
116+
[`diagrams/02-linked-list.mmd`](../diagrams/02-linked-list.mmd).
117+
118+
```mermaid
119+
flowchart LR
120+
title["Linked List: nodos enlazados por ownership"]
121+
122+
list["LinkedList<br/>head + len"]
123+
node0["Node A<br/>value + next"]
124+
node1["Node B<br/>value + next"]
125+
node2["Node C<br/>value + next"]
126+
none["None"]
127+
128+
list --> node0
129+
node0 --> node1
130+
node1 --> node2
131+
node2 --> none
132+
133+
push_front["push_front(X)<br/>nuevo nodo apunta al head anterior"]
134+
push_front --> list
135+
136+
push_back["push_back(X)<br/>recorre hasta next = None"]
137+
push_back --> node2
138+
```
139+
140+
## Análisis de complejidad
141+
142+
| Operación | Mejor caso | Caso promedio | Peor caso | Espacio |
143+
|-----------|------------|---------------|-----------|---------|
144+
| `new` | O(1) | O(1) | O(1) | O(1) |
145+
| `len` / `is_empty` | O(1) | O(1) | O(1) | O(1) |
146+
| `push_front` | O(1) | O(1) | O(1) | O(1) por nodo |
147+
| `push_back` | O(1) en lista vacía | O(n) | O(n) | O(1) por nodo |
148+
| `pop_front` | O(1) | O(1) | O(1) | O(1) |
149+
| `pop_back` | O(1) en 0/1 nodo | O(n) | O(n) | O(1) |
150+
| `front` | O(1) | O(1) | O(1) | O(1) |
151+
| `back` | O(1) en 0/1 nodo | O(n) | O(n) | O(1) |
152+
| `remove` | O(1) si índice 0 | O(n) | O(n) | O(1) |
153+
| `clear` | O(n) | O(n) | O(n) | O(1) adicional |
154+
| `iter` | O(1) crear, O(n) consumir | O(n) | O(n) | O(1) |
155+
156+
El costo de `push_back` es deliberado: al no guardar `tail`, la lista debe
157+
recorrer todos los nodos hasta encontrar `next == None`. Esa decisión simplifica
158+
la implementación segura y deja visible el tradeoff.
159+
160+
## Visualización interactiva (opcional)
161+
162+
No aplica todavía. El movimiento de nodos se entiende con diagrama, ejemplos y
163+
pruebas; una visualización se agregará cuando `academy-web` tenga definido el
164+
mecanismo de playgrounds.
165+
166+
## Implementación
167+
168+
La implementación completa vive en [`src/linked_list.rs`](../src/linked_list.rs).
169+
170+
El núcleo es:
171+
172+
```rust
173+
struct Node<T> {
174+
value: T,
175+
next: Option<Box<Node<T>>>,
176+
}
177+
```
178+
179+
`push_front` toma el `head` actual y lo convierte en `next` del nuevo nodo. Esa
180+
operación no recorre nada:
181+
182+
```rust
183+
let next = self.head.take();
184+
self.head = Some(Box::new(Node { value, next }));
185+
```
186+
187+
`push_back`, en cambio, camina por enlaces mutables hasta encontrar el hueco:
188+
189+
```rust
190+
let mut cursor = &mut self.head;
191+
while let Some(node) = cursor {
192+
cursor = &mut node.next;
193+
}
194+
*cursor = Some(new_node);
195+
```
196+
197+
El iterador guarda el próximo nodo como referencia:
198+
199+
```rust
200+
pub struct Iter<'a, T> {
201+
next: Option<&'a Node<T>>,
202+
}
203+
```
204+
205+
Cada llamada a `next()` devuelve el valor actual y avanza al enlace siguiente.
206+
No copia ni mueve valores.
207+
208+
## Pruebas
209+
210+
Las pruebas viven en [`tests/linked_list_test.rs`](../tests/linked_list_test.rs)
211+
y dentro del módulo de implementación.
212+
213+
Cubren:
214+
215+
- Lista vacía y transiciones de un solo elemento.
216+
- `push_front` / `pop_front`.
217+
- `push_back` / `pop_back`.
218+
- `front` y `back`.
219+
- Remoción de cabeza, medio, cola e índice inexistente.
220+
- Iteración en orden.
221+
- `clear` y destrucción de valores.
222+
- Consistencia de `len` tras operaciones mixtas.
223+
224+
Los doc-comments también se verifican con `cargo test --doc`.
225+
226+
## Benchmarks
227+
228+
El benchmark vive en
229+
[`benches/linked_list_bench.rs`](../benches/linked_list_bench.rs) y se ejecuta
230+
con:
231+
232+
```bash
233+
cargo bench --bench linked_list_bench
234+
```
235+
236+
Mide:
237+
238+
- `push_front`;
239+
- `push_back`;
240+
- `pop_front`;
241+
- iteración sobre lista enlazada;
242+
- iteración sobre vector como comparación de localidad.
243+
244+
El resultado esperado es que `push_front` y `pop_front` sean baratos, mientras
245+
que `push_back` sea mucho más caro por recorrer desde la cabeza. La comparación
246+
con vector ayuda a recordar que una estructura con operaciones asintóticas
247+
atractivas puede perder en práctica por localidad de memoria.
248+
249+
## Ejercicios
250+
251+
### Ejercicio 1: Invertir entrada `[Nivel 1]`
252+
253+
Construye una lista que reciba los valores `1, 2, 3, 4` y los deje en orden
254+
inverso usando solo `push_front`.
255+
256+
**Entrada/Salida esperada:** `[4, 3, 2, 1]`.
257+
258+
<details>
259+
<summary>Pista</summary>
260+
Cada nuevo valor insertado al frente queda antes de todos los anteriores.
261+
</details>
262+
263+
### Ejercicio 2: Mover frente al final `[Nivel 2]`
264+
265+
Implementa una función que tome el primer elemento de una lista y lo agregue al
266+
final.
267+
268+
**Entrada/Salida esperada:** `["a", "b", "c"]` queda como `["b", "c", "a"]`.
269+
270+
<details>
271+
<summary>Pista</summary>
272+
Combina `pop_front` con `push_back`.
273+
</details>
274+
275+
### Ejercicio 3: Remover alternados `[Nivel 3]`
276+
277+
Remueve cada segundo elemento de una lista. Para `1..=8`, el resultado debe ser
278+
`[1, 3, 5, 7]`.
279+
280+
**Entrada/Salida esperada:** `[1, 3, 5, 7]`.
281+
282+
<details>
283+
<summary>Pista</summary>
284+
Después de remover en un índice, el siguiente elemento se desplaza a ese mismo
285+
índice lógico.
286+
</details>
287+
288+
### Ejercicio 4: Cola de reintentos `[Nivel 4]`
289+
290+
Diseña una cola de reintentos para trabajos fallidos. Decide si basta esta lista
291+
simple, si necesitas guardar `tail`, o si conviene otra estructura. Explica las
292+
operaciones dominantes y sus costos.
293+
294+
**Entrada/Salida esperada:** no hay salida única; se evalúa el razonamiento.
295+
296+
<details>
297+
<summary>Pista</summary>
298+
Si insertas al final todo el tiempo, una lista simple sin `tail` te cobra O(n)
299+
por cada inserción.
300+
</details>
301+
302+
## Soluciones
303+
304+
Soluciones ejecutables de niveles 1 a 3:
305+
306+
- [`examples/soluciones/linked_list_reverse_input.rs`](../examples/soluciones/linked_list_reverse_input.rs)
307+
- [`examples/soluciones/linked_list_move_front_to_back.rs`](../examples/soluciones/linked_list_move_front_to_back.rs)
308+
- [`examples/soluciones/linked_list_remove_every_other.rs`](../examples/soluciones/linked_list_remove_every_other.rs)
309+
310+
Discusión para el nivel 4:
311+
312+
Una lista simple es adecuada si procesas por el frente y agregas poco al final.
313+
Si cada trabajo fallido se reencola al final constantemente, guardar un `tail`
314+
vuelve natural la operación, pero en Rust seguro ese diseño requiere más cuidado:
315+
un puntero crudo exigiría `unsafe`, y una solución con `Rc<RefCell<_>>` cambia el
316+
modelo de ownership. Un `VecDeque` puede ser la estructura práctica correcta si
317+
solo necesitas una cola eficiente en ambos extremos.
318+
319+
## Referencias
320+
321+
- Donald Knuth, *The Art of Computer Programming*, Vol. 1, secciones sobre
322+
estructuras enlazadas.
323+
- Robert Sedgewick y Kevin Wayne, *Algorithms*, secciones sobre listas enlazadas
324+
y pilas/colas.
325+
- Rust Standard Library, `std::collections::LinkedList`, documentación y notas de
326+
uso.
327+
- Rustonomicon, capítulos sobre ownership, punteros y por qué estructuras
328+
enlazadas avanzadas suelen requerir invariantes más estrictas.

docs/superpowers/plans/2026-07-14-rust-data-structures-course.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ For each chapter, complete the following checklist before moving to the next str
163163
- Create: `examples/linked_list_advanced.rs`
164164
- Create: `examples/linked_list_real_case.rs`
165165

166-
- [ ] Teach nodes, ownership, pointers, head/tail, singly vs doubly linked lists, traversal, insertion, removal, and iterator design.
167-
- [ ] Prefer safe Rust. Any `unsafe` requires a `// SAFETY:` invariant and a written justification in the chapter.
168-
- [ ] Compare against vector, deque, and intrusive lists.
169-
- [ ] Include tests for empty list, push front/back, pop front/back, remove, iteration, and single-element transitions.
170-
- [ ] Include benchmarks that show where linked lists lose to vectors because of locality.
166+
- [x] Teach nodes, ownership, pointers, head/tail, singly vs doubly linked lists, traversal, insertion, removal, and iterator design.
167+
- [x] Prefer safe Rust. Any `unsafe` requires a `// SAFETY:` invariant and a written justification in the chapter.
168+
- [x] Compare against vector, deque, and intrusive lists.
169+
- [x] Include tests for empty list, push front/back, pop front/back, remove, iteration, and single-element transitions.
170+
- [x] Include benchmarks that show where linked lists lose to vectors because of locality.
171171

172172
### Task 7: Stack
173173

0 commit comments

Comments
 (0)