Skip to content

Commit 4c246d6

Browse files
committed
feat: add stack chapter
1 parent ee04722 commit 4c246d6

5 files changed

Lines changed: 307 additions & 7 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ jobs:
2222
test -s docs/SUMMARY.md
2323
test -s docs/01-vector.md
2424
test -s docs/02-linked-list.md
25+
test -s docs/03-stack.md
2526
test -s examples/README.md
2627
test -s tests/README.md
2728
test -s benches/README.md
2829
test -s benches/vector_bench.rs
2930
test -s benches/linked_list_bench.rs
31+
test -s benches/stack_bench.rs
3032
test -s diagrams/README.md
3133
test -s diagrams/01-vector.mmd
3234
test -s diagrams/02-linked-list.mmd
35+
test -s diagrams/03-stack.mmd
3336
test -s assets/README.md
3437
3538
grep -q "Vector" docs/SUMMARY.md

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ un algoritmo sea necesario para explicar la propia estructura.
3232
|---|----------|--------|--------|
3333
| 01 | Vector | `src/vector.rs` | benchmarked |
3434
| 02 | Linked List | `src/linked_list.rs` | benchmarked |
35-
| 03 | Stack | `src/stack.rs` | planned |
35+
| 03 | Stack | `src/stack.rs` | benchmarked |
3636
| 04 | Queue | `src/queue.rs` | planned |
3737
| 05 | Deque | `src/deque.rs` | planned |
3838
| 06 | Heap | `src/heap.rs` | planned |

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ El checklist detallado vive en
2121
|---|----------|--------|
2222
| 01 | Vector | benchmarked |
2323
| 02 | Linked List | benchmarked |
24-
| 03 | Stack | planned |
24+
| 03 | Stack | benchmarked |
2525
| 04 | Queue | planned |
2626
| 05 | Deque | planned |
2727
| 06 | Heap | planned |

docs/03-stack.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Stack
2+
3+
> **Curso:** rust-data-structures · **Capítulo:** 03 · **Prerequisitos:** Capítulo 01, Vector; Capítulo 02, Linked List
4+
> **Código:** [`src/stack.rs`](../src/stack.rs) · **Video:** pendiente
5+
> **Lección en el sitio:** pendiente
6+
7+
## Introducción
8+
9+
Una pila (*stack*) es una colección LIFO: *last in, first out*. El último valor
10+
que entra es el primero que sale. Esa regla es más importante que la forma de
11+
almacenamiento; una pila puede respaldarse con un vector, una lista enlazada u
12+
otra estructura.
13+
14+
En este capítulo implementamos `Stack<T>` sobre el `Vector<T>` del capítulo 1.
15+
Así se ve una idea central del curso: muchas estructuras no son solo memoria,
16+
sino contratos de uso. El contrato de la pila es simple: `push`, `pop`, `peek`,
17+
underflow controlado y orden LIFO.
18+
19+
## Motivación
20+
21+
Cuando un editor ofrece "deshacer", guarda acciones en el orden en que ocurrieron,
22+
pero la siguiente acción que debe deshacer es la última. Cuando un parser revisa
23+
paréntesis, el cierre actual debe coincidir con la apertura más reciente. Cuando
24+
un recorrido usa DFS iterativo, el siguiente nodo sale del tope.
25+
26+
En todos esos casos no necesitamos insertar en medio ni buscar por índice. Solo
27+
queremos trabajar con el tope. Una pila existe para expresar ese patrón sin
28+
exponer operaciones que distraen o rompen la intención.
29+
30+
## Teoría
31+
32+
### Historia
33+
34+
Las pilas aparecen en arquitectura de computadoras, lenguajes de programación y
35+
algoritmos desde muy temprano. La pila de llamadas guarda frames de funciones:
36+
la función más reciente debe terminar antes de volver a la anterior. Las máquinas
37+
virtuales basadas en pila evalúan expresiones empujando operandos y aplicando
38+
operaciones sobre el tope.
39+
40+
Su valor histórico y práctico viene de una restricción: al limitar el acceso al
41+
tope, la estructura se vuelve fácil de razonar.
42+
43+
### Fundamentos
44+
45+
La pila expone estas operaciones:
46+
47+
- `push(value)`: agrega un valor al tope.
48+
- `pop()`: remueve el tope y lo devuelve.
49+
- `peek()`: lee el tope sin removerlo.
50+
- `peek_mut()`: modifica el tope sin cambiar la profundidad.
51+
- `clear()`: vacía la pila.
52+
- `iter()`: recorre desde la base hasta el tope.
53+
54+
La invariante semántica es:
55+
56+
```text
57+
si push(a) ocurre antes que push(b), entonces pop() devuelve b antes que a
58+
```
59+
60+
Nuestra implementación guarda el tope al final del vector interno. Eso hace que
61+
`push` use `Vector::push` y `pop` use `Vector::pop`, ambas operaciones eficientes
62+
para el final de la colección.
63+
64+
### Casos de uso
65+
66+
Usos clásicos:
67+
68+
- Undo/redo.
69+
- Validación de paréntesis y delimitadores.
70+
- Evaluación de expresiones.
71+
- DFS iterativo.
72+
- Historial de navegación.
73+
- Call stack conceptual de un programa.
74+
75+
### Ventajas y limitaciones
76+
77+
Ventajas:
78+
79+
- API mínima y clara.
80+
- `push`, `pop` y `peek` baratos.
81+
- Underflow representado con `Option<T>`, sin pánico.
82+
- Fácil de respaldar con diferentes estructuras.
83+
84+
Limitaciones:
85+
86+
- No permite acceso arbitrario sin romper la abstracción.
87+
- Iterar expone lectura, pero no debe reemplazar el contrato LIFO.
88+
- Un vector puede mover memoria al crecer.
89+
- Una lista enlazada puede evitar crecimiento por bloque, pero paga asignaciones
90+
por nodo y peor localidad.
91+
92+
### Comparación con alternativas
93+
94+
Una pila respaldada por vector aprovecha memoria contigua y `push/pop` al final.
95+
Una pila respaldada por lista enlazada puede hacer `push_front/pop_front` en O(1),
96+
pero asigna un nodo por valor. En la práctica, para valores pequeños y muchos
97+
recorridos, la localidad del vector suele ganar.
98+
99+
Un deque también puede actuar como pila, pero expone más operaciones de las que
100+
el concepto necesita. La decisión no es solo rendimiento: una API más pequeña
101+
enseña y protege la intención.
102+
103+
## Diagramas
104+
105+
El diagrama principal vive en [`diagrams/03-stack.mmd`](../diagrams/03-stack.mmd).
106+
107+
```mermaid
108+
flowchart TB
109+
title["Stack: LIFO sobre almacenamiento interno"]
110+
111+
top["top"]
112+
slot2["2: C"]
113+
slot1["1: B"]
114+
slot0["0: A"]
115+
base["base"]
116+
117+
top --> slot2
118+
slot2 --> slot1
119+
slot1 --> slot0
120+
slot0 --> base
121+
122+
push["push(D)<br/>agrega sobre top"]
123+
pop["pop()<br/>remueve top"]
124+
peek["peek()<br/>lee top sin remover"]
125+
126+
push --> top
127+
pop --> top
128+
peek --> top
129+
```
130+
131+
## Análisis de complejidad
132+
133+
| Operación | Mejor caso | Caso promedio | Peor caso | Espacio |
134+
|-----------|------------|---------------|-----------|---------|
135+
| `new` | O(1) | O(1) | O(1) | O(1) |
136+
| `with_capacity(n)` | O(n) | O(n) | O(n) | O(n) |
137+
| `len` / `capacity` / `is_empty` | O(1) | O(1) | O(1) | O(1) |
138+
| `push` | O(1) | O(1) amortizado | O(n) si crece | O(n) si crece |
139+
| `pop` | O(1) | O(1) | O(1) | O(1) |
140+
| `peek` / `peek_mut` | O(1) | O(1) | O(1) | O(1) |
141+
| `clear` | O(n) | O(n) | O(n) | O(1) |
142+
| `iter` | O(1) crear, O(n) consumir | O(n) | O(n) | O(1) |
143+
144+
El peor caso de `push` aparece cuando el vector interno debe crecer. Aun así, el
145+
costo amortizado es O(1), igual que en el capítulo de `Vector`.
146+
147+
## Visualización interactiva (opcional)
148+
149+
No aplica todavía. La pila se entiende con el diagrama, los ejemplos de
150+
undo/parsing y los benchmarks; se agregará playground cuando `academy-web` tenga
151+
ese mecanismo definido.
152+
153+
## Implementación
154+
155+
La implementación vive en [`src/stack.rs`](../src/stack.rs).
156+
157+
El tipo es pequeño:
158+
159+
```rust
160+
pub struct Stack<T> {
161+
items: Vector<T>,
162+
}
163+
```
164+
165+
`push` delega al final del vector:
166+
167+
```rust
168+
pub fn push(&mut self, value: T) {
169+
self.items.push(value);
170+
}
171+
```
172+
173+
`peek` calcula el último índice sin restar sobre cero:
174+
175+
```rust
176+
self.items
177+
.len()
178+
.checked_sub(1)
179+
.and_then(|index| self.items.get(index))
180+
```
181+
182+
Ese detalle importa: una pila vacía no debe provocar underflow aritmético ni
183+
pánico. Devuelve `None`, porque no hay tope.
184+
185+
## Pruebas
186+
187+
Las pruebas viven en [`tests/stack_test.rs`](../tests/stack_test.rs) y dentro de
188+
[`src/stack.rs`](../src/stack.rs).
189+
190+
Cubren:
191+
192+
- Underflow: `pop` y `peek` sobre pila vacía.
193+
- Orden LIFO.
194+
- `peek` sin remover.
195+
- `peek_mut` modificando solo el tope.
196+
- `clear` conservando capacidad.
197+
- Iteración desde base hasta tope.
198+
- Movimiento de ownership con `pop`.
199+
- Destrucción de valores restantes con `clear`.
200+
201+
Los doc-comments se validan con `cargo test --doc`.
202+
203+
## Benchmarks
204+
205+
El benchmark vive en [`benches/stack_bench.rs`](../benches/stack_bench.rs) y se
206+
ejecuta con:
207+
208+
```bash
209+
cargo bench --bench stack_bench
210+
```
211+
212+
Mide:
213+
214+
- `push/pop` con stack respaldado por vector;
215+
- `push_front/pop_front` con lista enlazada como comparación;
216+
- `peek` repetido.
217+
218+
La comparación existe para enseñar tradeoffs. Ambas estrategias pueden sostener
219+
operaciones LIFO baratas, pero el vector suele tener mejor localidad y la lista
220+
evita crecimiento por bloque a cambio de asignar nodos.
221+
222+
## Ejercicios
223+
224+
### Ejercicio 1: Trazar operaciones `[Nivel 1]`
225+
226+
Ejecuta la secuencia `push(A)`, `push(B)`, `pop()` y registra el tope después de
227+
cada paso.
228+
229+
**Entrada/Salida esperada:** `[Some("A"), Some("B"), Some("A")]`.
230+
231+
<details>
232+
<summary>Pista</summary>
233+
`pop` remueve `B`, por eso `A` vuelve a quedar como tope.
234+
</details>
235+
236+
### Ejercicio 2: Paréntesis balanceados `[Nivel 2]`
237+
238+
Implementa una función que determine si una cadena tiene paréntesis, corchetes y
239+
llaves balanceados.
240+
241+
**Entrada/Salida esperada:** `([]{})` es válido; `([)]` no lo es.
242+
243+
<details>
244+
<summary>Pista</summary>
245+
Empuja aperturas y compara cada cierre contra la apertura más reciente.
246+
</details>
247+
248+
### Ejercicio 3: Undo/redo `[Nivel 3]`
249+
250+
Modela dos pilas: una para undo y otra para redo. Mover el tope de una pila a la
251+
otra debe preservar el valor exacto.
252+
253+
**Entrada/Salida esperada:** deshacer y rehacer una acción deja la pila de undo
254+
con esa acción nuevamente en el tope.
255+
256+
<details>
257+
<summary>Pista</summary>
258+
`pop` transfiere ownership; no necesitas clonar.
259+
</details>
260+
261+
### Ejercicio 4: Parser iterativo `[Nivel 4]`
262+
263+
Diseña un parser o recorrido de árbol que use una pila explícita en vez de
264+
recursión. Explica qué guardas en cada frame y por qué el orden LIFO es correcto.
265+
266+
**Entrada/Salida esperada:** no hay una única solución; se evalúa el diseño y
267+
sus invariantes.
268+
269+
<details>
270+
<summary>Pista</summary>
271+
Piensa en reemplazar el call stack por una estructura que tú controlas.
272+
</details>
273+
274+
## Soluciones
275+
276+
Soluciones ejecutables de niveles 1 a 3:
277+
278+
- [`examples/soluciones/stack_trace_operations.rs`](../examples/soluciones/stack_trace_operations.rs)
279+
- [`examples/soluciones/stack_balanced_parentheses.rs`](../examples/soluciones/stack_balanced_parentheses.rs)
280+
- [`examples/soluciones/stack_undo_redo.rs`](../examples/soluciones/stack_undo_redo.rs)
281+
282+
Discusión para el nivel 4:
283+
284+
Una pila explícita es útil cuando quieres controlar el recorrido, evitar
285+
recursión profunda o guardar estado adicional por frame. El tradeoff es que el
286+
programa debe hacer visible lo que antes hacía el call stack: qué falta visitar,
287+
qué resultado parcial existe y cuándo se termina un frame.
288+
289+
## Referencias
290+
291+
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein,
292+
*Introduction to Algorithms*, secciones sobre pilas, colas y DFS.
293+
- Robert Sedgewick y Kevin Wayne, *Algorithms*, secciones introductorias de
294+
stacks y expresión de algoritmos con LIFO.
295+
- Rust Standard Library, `Vec<T>` y `Option<T>`, como base para una pila segura.
296+
- Rust Book, capítulos de ownership y borrowing, para entender por qué `pop`
297+
transfiere valores.

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
@@ -182,11 +182,11 @@ For each chapter, complete the following checklist before moving to the next str
182182
- Create: `examples/stack_advanced.rs`
183183
- Create: `examples/stack_real_case.rs`
184184

185-
- [ ] Teach LIFO, push, pop, peek, underflow, implementation over vector, and why stack is an interface more than a storage strategy.
186-
- [ ] Compare vector-backed stack against linked-list-backed stack.
187-
- [ ] Include real cases: call stack, undo history, expression parsing.
188-
- [ ] Include tests for empty pop, peek, LIFO order, clear, and mixed operations.
189-
- [ ] Include benchmarks for vector-backed operations and explain why most are O(1).
185+
- [x] Teach LIFO, push, pop, peek, underflow, implementation over vector, and why stack is an interface more than a storage strategy.
186+
- [x] Compare vector-backed stack against linked-list-backed stack.
187+
- [x] Include real cases: call stack, undo history, expression parsing.
188+
- [x] Include tests for empty pop, peek, LIFO order, clear, and mixed operations.
189+
- [x] Include benchmarks for vector-backed operations and explain why most are O(1).
190190

191191
### Task 8: Queue
192192

0 commit comments

Comments
 (0)