intermedio⏱45 minutosLección 6 de 10
Vectores, Strings y HashMaps
Domine los tipos de colección estándar de Rust con operaciones comunes, iteración y la API Entry
Vectores, Strings y HashMaps
La biblioteca estándar de Rust proporciona colecciones potentes y eficientes. Entender sus internos y APIs es esencial para el desarrollo productivo en Rust.
Vec<T> — Arrays Dinámicos
Los vectores almacenan elementos del mismo tipo en memoria heap contigua:
rust
fn main() {
// Creación
let mut v1: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];
let v3 = vec![0; 5]; // [0, 0, 0, 0, 0]
// Añadir y remover
v1.push(10);
v1.push(20);
v1.push(30);
let last = v1.pop(); // Some(30)
let first = v1.remove(0); // 10
// Acceso
let third = &v2[2]; // 3 — panic si está fuera de límites
let safe = v2.get(2); // Some(&3) — retorna Option
let out = v2.get(100); // None — seguro
// Actualización
if let Some(x) = v1.get_mut(0) {
*x = 42;
}
}Note
Iteración
rust
fn main() {
let v = vec![1, 2, 3];
// Iteración inmutable
for x in &v {
println!("{x}");
}
// Iteración mutable
let mut v = vec![1, 2, 3];
for x in &mut v {
*x *= 2;
}
// Iteración consumiendo
for x in v { // v es movido
println!("{x}");
}
// println!("{:?}", v); // ERROR: v movido
// Varios adaptadores
let doubled: Vec<i32> = vec![1, 2, 3].iter().map(|x| x * 2).collect();
let evens: Vec<&i32> = vec![1, 2, 3, 4].iter().filter(|x| *x % 2 == 0).collect();
let sum: i32 = vec![1, 2, 3].iter().sum();
}Capacidad del Vector
rust
fn main() {
let mut v = Vec::with_capacity(100);
println!("len: {}, cap: {}", v.len(), v.capacity()); // 0, 100
v.push(1);
println!("len: {}, cap: {}", v.len(), v.capacity()); // 1, 100
v.shrink_to_fit();
println!("len: {}, cap: {}", v.len(), v.capacity()); // 1, 1
// Reservar más
v.reserve(1000);
println!("cap: {}", v.capacity()); // 1001
}Success
Pre-asigna con Vec::with_capacity(n) cuando sepas el tamaño esperado. Esto evita reasignaciones repetidas.
String — Texto UTF-8
String es un Vec<u8> que garantiza UTF-8 válido:
rust
fn main() {
// Creación
let mut s = String::new();
let s1 = String::from("hello");
let s2 = "world".to_string();
let s3 = format!("{s1} {s2}");
// Añadir
s.push('!'); // Carácter único
s.push_str(" world"); // Porción de string
// Concatenación
let combined = s1 + &s2; // s1 movido, &s2 prestado
// println!("{s1}"); // ERROR: movido
let combined = format!("{} {}", "hello", "world"); // Sin move
// Indexación — NO soportada por índice de char
// let c = s[0]; // ERROR: String no soporta index
}Warning
Las strings no soportan indexación O(1) porque UTF-8 tiene ancho variable. s[i] sería O(n) y podría dividir un carácter. Usa .chars() o .bytes().
Operaciones con String
rust
fn main() {
let s = "hello world".to_string();
// Iteración
for c in s.chars() { // Grafemas Unicode
print!("{c} ");
}
println!();
for b in s.bytes() { // Bytes brutos
print!("{b} ");
}
println!();
// Segmentación (¡cuidado!)
let hello = &s[0..5]; // "hello"
// let bad = &s[0..4]; // PANICS: no está en límite de char
// Búsqueda y sustitución
println!("contains 'world': {}", s.contains("world"));
let replaced = s.replace("world", "Rust");
println!("{replaced}");
// División
let words: Vec<&str> = s.split(' ').collect();
println!("{:?}", words);
// Recorte
let padded = " hello ".to_string();
println!("'{}'", padded.trim());
}String vs &str
| Aspecto | String | &str |
|---|---|---|
| Propiedad | Sí | No (prestado) |
| Mutable | Sí | No |
| Asignada en heap | Sí | Apunta a memoria existente |
| Tamaño | len() en bytes | len() en bytes |
| Uso en parámetros | Prefiere &str | Prefiere &str |
Construyendo Strings Eficientemente
rust
fn main() {
// Malo: muchas asignaciones
let mut s = String::new();
for i in 0..100 {
s = s + &i.to_string(); // Nueva asignación cada vez!
}
// Bueno: pre-asignar
let mut s = String::with_capacity(300);
for i in 0..100 {
s.push_str(&i.to_string());
}
// Mejor: usar collect
let s: String = (0..100).map(|i| i.to_string()).collect();
}HashMap<K, V> — Almacenamiento Clave-Valor
rust
use std::collections::HashMap;
fn main() {
// Creación
let mut scores: HashMap<String, i32> = HashMap::new();
// Insertar
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 90);
// Acceso
let alice = scores.get("Alice"); // Some(&100)
let charlie = scores.get("Charlie"); // None
// Iterar
for (name, score) in &scores {
println!("{name}: {score}");
}
// Verificar existencia
if scores.contains_key("Alice") {
println!("Alice is in the map");
}
// Remover
scores.remove("Bob");
}La API Entry
La forma más idiomática de trabajar con HashMaps:
rust
use std::collections::HashMap;
fn main() {
let mut word_count = HashMap::new();
let text = "hello world hello Rust hello";
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", word_count);
// {"hello": 3, "world": 1, "Rust": 1}
}| Método Entry | Comportamiento |
|---|---|
or_insert(v) | Inserta v si ausente, retorna &mut V |
or_insert_with(fn) | Inserta resultado de fn() si ausente |
or_default() | Inserta V::default() si ausente |
and_modify(fn) | Modifica valor existente si presente |
Consejos de Rendimiento para HashMap
rust
use std::collections::HashMap;
fn main() {
// Pre-asignar capacidad
let mut map: HashMap<&str, i32> = HashMap::with_capacity(1000);
// Usar entry API para insertar-o-modificar
map.entry("key").and_modify(|v| *v += 1).or_insert(1);
// Hasher personalizado (para rendimiento)
use std::collections::hash_map::RandomState;
let fast_map: HashMap<&str, i32, RandomState> = HashMap::default();
}Otras Colecciones Útiles
| Colección | Descripción | Caso de Uso |
|---|---|---|
VecDeque<T> | Cola doblemente terminada | FIFO, push/pop ambos extremos |
LinkedList<T> | Lista doblemente enlazada | Raro, fragmentación pesada |
HashSet<T> | Conjunto de valores únicos | Desduplicación, pertenencia |
BTreeMap<K, V> | Clave-valor ordenado | Iteración ordenada |
BTreeSet<T> | Conjunto ordenado | Valores únicos ordenados |
BinaryHeap<T> | Cola de prioridad | Max-heap, procesamiento por prioridad |
rust
use std::collections::{VecDeque, HashSet, BTreeMap, BinaryHeap};
fn main() {
// VecDeque — cola eficiente
let mut queue = VecDeque::new();
queue.push_back(1);
queue.push_front(0);
assert_eq!(queue.pop_front(), Some(0));
// HashSet — prueba rápida de pertenencia
let mut seen = HashSet::new();
seen.insert("hello");
assert!(seen.contains("hello"));
// BTreeMap — claves ordenadas
let mut sorted = BTreeMap::new();
sorted.insert("b", 2);
sorted.insert("a", 1);
for (k, v) in &sorted {
println!("{k}: {v}"); // a: 1, b: 2 (ordenado)
}
// BinaryHeap — max heap
let mut heap = BinaryHeap::new();
heap.push(3);
heap.push(1);
heap.push(5);
assert_eq!(heap.pop(), Some(5)); // Mayor primero
}Ejemplo Real: Analizador de Frecuencia de Palabras
rust
use std::collections::HashMap;
fn word_frequency(text: &str) -> Vec<(String, usize)> {
let mut freq: HashMap<String, usize> = HashMap::new();
for word in text
.to_lowercase()
.split(|c: char| !c.is_alphanumeric())
.filter(|w| !w.is_empty())
{
*freq.entry(word.to_string()).or_insert(0) += 1;
}
let mut result: Vec<_> = freq.into_iter().collect();
result.sort_by(|a, b| b.1.cmp(&a.1));
result.truncate(10);
result
}
fn main() {
let text = "The quick brown fox jumps over the lazy dog. The dog slept. The fox jumped again.";
let top_words = word_frequency(text);
for (word, count) in top_words {
println!("{word}: {count}");
}
}Preguntas de Práctica
- ¿Cuál es la diferencia entre
Vec::new()yvec![]? - ¿Cómo acceder seguramente a un elemento de vector sin panic?
- ¿Qué es la capacidad de un vector y por qué importa?
- ¿Por qué
Stringno soporta indexación por posición de carácter? - ¿Cómo iterar sobre los caracteres de una String?
- ¿Cuándo usar
&strvsStringen parámetros de función? - ¿Qué proporciona la API Entry que insert directo no proporciona?
- ¿Cómo actualizar un valor en un HashMap si existe, o insertar si no existe?
- ¿Qué colección usarías para una cola de prioridad?
- ¿Cómo pre-asignar un HashMap para mejor rendimiento?
Progreso60%