VecDeque y BinaryHeap en Rust: colas y colas de prioridad
Piensa en la fila de una cafetería. La gente nueva se pone al final, y el barista atiende a quien está delante. Ahora piensa en la sala de espera de un hospital. Ahí pasa primero el paciente más urgente, da igual cuándo llegó. Las dos son “filas de espera”, pero siguen reglas distintas.
Rust tiene una colección para cada una. Un VecDeque es una fila rápida en la que puedes añadir y quitar por los dos extremos. Un BinaryHeap es una cola de prioridad: siempre te da el elemento más grande (o más urgente). Ya conoces los vectores, y la página de HashSet te mostró VecDeque por encima. Esta página profundiza en él y además te presenta BinaryHeap.
Por qué un Vec es una mala cola
Sección titulada «Por qué un Vec es una mala cola»Puedes usar un Vec como cola. Añades con push y sacas del principio con remove(0). Funciona. El problema es lo que pasa por debajo.
Un Vec guarda sus elementos juntos, empezando en la posición 0. Cuando quitas el primero, todos los demás tienen que moverse un paso a la izquierda para tapar el hueco. Con 3 elementos, nadie lo nota. Con 100 000 elementos, cada remove(0) mueve 99 999 valores. Eso es lo que significa “O(n)”: el trabajo crece con el tamaño de la lista.
Un VecDeque (se pronuncia “vec dek”, de double-ended queue, cola de doble extremo) lo resuelve con un ring buffer (búfer circular). Imagina una mesa redonda con asientos numerados y un cartelito que dice “la fila empieza aquí”. Cuando se va la primera persona, nadie se levanta. Solo mueves el cartel al siguiente asiento. Cuando llega alguien nuevo y el último asiento está ocupado, se sienta en el asiento 0 si está libre, dando la vuelta a la mesa. Añadir o quitar por cualquiera de los dos extremos es solo “mover el cartel” u “ocupar un asiento”, así que tarda lo mismo, muy poquito, sea cual sea el largo de la fila. Eso es “O(1)”.
use std::collections::VecDeque;
fn main() { // Un Vec usado como cola: funciona, pero remove(0) mueve a todos a la izquierda. let mut vec_line = vec!["Ana", "Ben", "Cai"]; let served = vec_line.remove(0); println!("Vec atendió a {served}, siguen esperando: {:?}", vec_line);
// Un VecDeque: pop_front solo mueve su marca de "inicio". let mut deque_line = VecDeque::from(vec!["Ana", "Ben", "Cai"]); let served = deque_line.pop_front(); println!("VecDeque atendió a {:?}, siguen esperando: {:?}", served, deque_line);}Salida:
Vec atendió a Ana, siguen esperando: ["Ben", "Cai"]VecDeque atendió a Some("Ana"), siguen esperando: ["Ben", "Cai"]El resultado se ve igual. La diferencia es la velocidad, y solo se nota cuando la fila se hace larga. Fíjate en otra cosa: pop_front devuelve un Option. Si la fila está vacía, obtienes None en lugar de que el programa falle.
Lo básico de VecDeque
Sección titulada «Lo básico de VecDeque»VecDeque está en std::collections, así que necesitas una línea use. Después, los métodos se leen casi solos: push (meter) o pop (sacar), por delante (front) o por detrás (back).
use std::collections::VecDeque;
fn main() { let mut deque: VecDeque<i32> = VecDeque::new(); deque.push_back(2); deque.push_back(3); deque.push_front(1); println!("{:?}", deque);
println!("delante: {:?}, detrás: {:?}", deque.front(), deque.back()); println!("len: {}, vacío: {}", deque.len(), deque.is_empty()); println!("deque[1] = {}", deque[1]); println!("get(9) = {:?}", deque.get(9));
let first = deque.pop_front(); let last = deque.pop_back(); println!("sacados {:?} y {:?}, quedan: {:?}", first, last, deque);}Salida:
[1, 2, 3]delante: Some(1), detrás: Some(3)len: 3, vacío: falsedeque[1] = 2get(9) = Nonesacados Some(1) y Some(3), quedan: [2]front y back te dejan mirar sin quitar nada. Indexar con deque[1] funciona igual que en un Vec, y provoca un panic si el índice es demasiado grande, así que usa get cuando no estés seguro.
También puedes crear un VecDeque a partir de un Vec, recorrerlo con un bucle y volver a convertirlo en un Vec o en un slice cuando otra función lo necesite:
use std::collections::VecDeque;
fn main() { let mut playlist = VecDeque::from(vec!["Intro", "Estrofa", "Estribillo"]); playlist.push_front("Conteo"); playlist.push_back("Cierre");
for (i, song) in playlist.iter().enumerate() { println!("{}. {}", i + 1, song); }
// ¿Necesitas un slice normal? make_contiguous lo pone todo seguido en memoria. let songs = playlist.make_contiguous(); songs.sort(); println!("ordenadas: {:?}", songs);
// O convierte todo de nuevo en un Vec. let as_vec: Vec<&str> = playlist.into(); println!("como Vec: {:?}", as_vec);}Salida:
1. Conteo2. Intro3. Estrofa4. Estribillo5. Cierreordenadas: ["Cierre", "Conteo", "Estribillo", "Estrofa", "Intro"]como Vec: ["Cierre", "Conteo", "Estribillo", "Estrofa", "Intro"]¿Por qué hace falta make_contiguous? Acuérdate de la mesa redonda: después de dar algunas vueltas, los elementos pueden quedar en dos trozos separados de memoria. make_contiguous los ordena en un solo trozo y te da un slice normal, para que puedas usar herramientas de slices como sort. No lo necesitarás a menudo, pero conviene saber que existe.
Usos reales de VecDeque
Sección titulada «Usos reales de VecDeque»Aquí tienes tres tareas del día a día en las que un VecDeque encaja perfecto.
Una cola de tareas en la que el primero en entrar es el primero en salir. Los trabajos de impresión esperan en fila. Los nuevos van al final y la impresora toma el de delante. Aun así, un trabajo urgente puede colarse con push_front:
use std::collections::VecDeque;
fn main() { let mut print_queue = VecDeque::new(); print_queue.push_back("informe.pdf"); print_queue.push_back("foto.jpg"); print_queue.push_back("factura.pdf");
// ¡Urgente! Este se pone el primero de la fila. print_queue.push_front("tarjeta-de-embarque.pdf");
while let Some(job) = print_queue.pop_front() { println!("Imprimiendo {job} (en espera: {})", print_queue.len()); } println!("Listo. Cola vacía: {}", print_queue.is_empty());}Salida:
Imprimiendo tarjeta-de-embarque.pdf (en espera: 3)Imprimiendo informe.pdf (en espera: 2)Imprimiendo foto.jpg (en espera: 1)Imprimiendo factura.pdf (en espera: 0)Listo. Cola vacía: truewhile let Some(job) = ...pop_front() es el bucle clásico de una cola. Sigue hasta que pop_front devuelve None, que significa que la fila está vacía. (Si esta forma es nueva para ti, mira if let).
Un historial de “los últimos N elementos”. Un buscador que recuerda tus 3 búsquedas más recientes. Cuando está lleno, la más antigua se cae por delante:
use std::collections::VecDeque;
const MAX_RECENT: usize = 3;
fn remember(recent: &mut VecDeque<String>, search: &str) { if recent.len() == MAX_RECENT { recent.pop_front(); // olvida la búsqueda más antigua } recent.push_back(search.to_string());}
fn main() { let mut recent = VecDeque::with_capacity(MAX_RECENT);
for search in ["libro de rust", "vecdeque", "binary heap", "pizza cerca de mí"] { remember(&mut recent, search); println!("{:?}", recent); }
// Muestra primero la más nueva, como haría un buscador. let newest_first: Vec<&String> = recent.iter().rev().collect(); println!("más nuevas primero: {:?}", newest_first);}Salida:
["libro de rust"]["libro de rust", "vecdeque"]["libro de rust", "vecdeque", "binary heap"]["vecdeque", "binary heap", "pizza cerca de mí"]más nuevas primero: ["pizza cerca de mí", "binary heap", "vecdeque"]Una media con ventana deslizante. Un termómetro envía una lectura cada minuto y quieres la media de las 3 últimas. Cada lectura nueva entra por detrás y la más antigua sale por delante. Si llevas una sum (suma) acumulada, nunca tienes que volver a sumar toda la ventana:
use std::collections::VecDeque;
fn main() { let readings = [20.0, 22.0, 24.0, 23.0, 19.0, 21.0]; let window_size = 3; let mut window: VecDeque<f64> = VecDeque::new(); let mut sum = 0.0;
for reading in readings { window.push_back(reading); sum += reading;
if window.len() > window_size { sum -= window.pop_front().unwrap(); // sale la lectura más antigua }
if window.len() == window_size { let average = sum / window_size as f64; println!("{:?} -> media {:.1}", window, average); } }}Salida:
[20.0, 22.0, 24.0] -> media 22.0[22.0, 24.0, 23.0] -> media 23.0[24.0, 23.0, 19.0] -> media 22.0[23.0, 19.0, 21.0] -> media 21.0Aquí el unwrap() es seguro: solo sacamos cuando la ventana tiene más de 3 elementos, así que no puede estar vacía.
BinaryHeap: siempre el más grande primero
Sección titulada «BinaryHeap: siempre el más grande primero»Ahora, la sala de espera del hospital. A veces no te importa quién llegó primero. Lo que quieres es “dame lo más importante”, una y otra vez, mientras siguen llegando cosas nuevas.
Podrías meter todo en un Vec y ordenarlo cada vez. Pero ordenar todo solo para encontrar el primero es mucho trabajo desperdiciado. Un BinaryHeap (montículo binario) mantiene sus elementos organizados solo a medias, lo justo para que el más grande esté siempre arriba. Añadir un elemento o sacar el de arriba es barato (O(log n), que crece muy despacio: unos 17 pasos para 100 000 elementos).
El BinaryHeap de Rust es un max-heap (montículo de máximos): pop siempre te da el valor más grande. Aquí tienes las pujas de una subasta:
use std::collections::BinaryHeap;
fn main() { let mut bids = BinaryHeap::new(); bids.push(120); bids.push(450); bids.push(80); bids.push(300);
println!("puja más alta: {:?}", bids.peek()); println!("número de pujas: {}", bids.len());
while let Some(bid) = bids.pop() { println!("siguiente: {bid}"); }
let heap = BinaryHeap::from(vec![5, 1, 4, 2, 3]); println!("ordenado: {:?}", heap.into_sorted_vec());}Salida:
puja más alta: Some(450)número de pujas: 4siguiente: 450siguiente: 300siguiente: 120siguiente: 80ordenado: [1, 2, 3, 4, 5]peek mira el de arriba sin quitarlo. pop lo quita. into_sorted_vec consume el heap y te da un Vec ordenado de menor a mayor. Igual que en VecDeque, peek y pop devuelven un Option, así que un heap vacío nunca hace fallar el programa.
Min-heaps con Reverse
Sección titulada «Min-heaps con Reverse»¿Y si quieres el más pequeño primero? La parada de reparto más cercana, el vuelo más barato, la fecha límite más próxima. No necesitas otra colección. Envuelve cada valor en std::cmp::Reverse, que simplemente invierte la comparación: lo que era más grande ahora es más pequeño.
use std::cmp::Reverse;use std::collections::BinaryHeap;
fn main() { // Paradas de reparto, por distancia en km. Siempre vas a la más cercana. let mut stops = BinaryHeap::new(); for km in [12, 3, 8, 5] { stops.push(Reverse(km)); }
if let Some(Reverse(closest)) = stops.peek() { println!("parada más cercana: {closest} km"); }
while let Some(Reverse(km)) = stops.pop() { println!("conduce {km} km"); }}Salida:
parada más cercana: 3 kmconduce 3 kmconduce 5 kmconduce 8 kmconduce 12 kmReverse(km) es un struct envoltorio muy pequeño. El patrón Some(Reverse(km)) quita las dos capas de una vez, así que recuperas el número tal cual.
Una cola de prioridad de structs
Sección titulada «Una cola de prioridad de structs»Las tareas reales no son solo números. Tienen un nombre, una prioridad y quizá una hora de llegada. Para meter tu propio struct en un BinaryHeap, Rust necesita saber cómo comparar dos de ellos, lo que significa que el struct debe implementar Ord (consulta Traits comunes).
La forma más rápida es derive. Una cosa que debes saber: derive compara los campos de arriba abajo. Compara el primer campo y solo mira el segundo si los primeros son iguales. Así que pon primero el campo por el que quieres ordenar:
use std::collections::BinaryHeap;
// El orden de los campos importa: derive compara primero `priority` y luego `name`.#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]struct Task { priority: u8, name: String,}
fn main() { let mut todo = BinaryHeap::new(); todo.push(Task { priority: 2, name: "pedir cita con el dentista".to_string() }); todo.push(Task { priority: 5, name: "arreglar la caída del servidor".to_string() }); todo.push(Task { priority: 1, name: "regar las plantas".to_string() }); todo.push(Task { priority: 2, name: "responder el correo".to_string() });
while let Some(task) = todo.pop() { println!("[{}] {}", task.priority, task.name); }}Salida:
[5] arreglar la caída del servidor[2] responder el correo[2] pedir cita con el dentista[1] regar las plantasSi cambiaras el orden de los dos campos para que name fuera primero, el heap ordenaría las tareas alfabéticamente y casi ignoraría la prioridad. Un error fácil de cometer, y un bug silencioso.
Mira las dos tareas con prioridad 2. “pedir cita con el dentista” llegó antes, pero “responder el correo” salió primero. El empate lo decidió name, y “responder” gana a “pedir” porque va después en el alfabeto. Es predecible, pero seguramente no es lo que quieres. Normalmente, en un empate, debería ir primero la tarea que llegó antes. Para una regla personalizada así, escribe Ord tú mismo:
use std::cmp::Ordering;use std::collections::BinaryHeap;
#[derive(Debug, PartialEq, Eq)]struct Job { priority: u8, id: u32, // orden de llegada: un id más bajo llegó antes name: String,}
impl Ord for Job { fn cmp(&self, other: &Self) -> Ordering { self.priority .cmp(&other.priority) // gana la prioridad más alta .then_with(|| other.id.cmp(&self.id)) // en un empate, gana el id más antiguo }}
impl PartialOrd for Job { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }}
fn main() { let mut jobs = BinaryHeap::new(); jobs.push(Job { priority: 2, id: 1, name: "pedir cita con el dentista".to_string() }); jobs.push(Job { priority: 5, id: 2, name: "arreglar la caída del servidor".to_string() }); jobs.push(Job { priority: 1, id: 3, name: "regar las plantas".to_string() }); jobs.push(Job { priority: 2, id: 4, name: "responder el correo".to_string() });
while let Some(job) = jobs.pop() { println!("[{}] #{} {}", job.priority, job.id, job.name); }}Salida:
[5] #2 arreglar la caída del servidor[2] #1 pedir cita con el dentista[2] #4 responder el correo[1] #3 regar las plantasEl truco está en then_with: si las prioridades son iguales, compara los ids al revés (other.id contra self.id). Así el id más pequeño cuenta como “más grande” y sale primero del max-heap. Tranquilo si te cuesta un poco entenderlo. Invertir la comparación es justo lo que Reverse hace por ti, solo que aquí está escrito a mano. El bloque de PartialOrd es siempre el mismo código repetitivo: solo llama a tu cmp.
Un algoritmo real: las 3 mejores puntuaciones
Sección titulada «Un algoritmo real: las 3 mejores puntuaciones»Imagina que tienes una lista enorme de puntuaciones de un juego y solo quieres las 3 mejores. Ordenar toda la lista funciona, pero hay un truco muy útil que solo guarda 3 elementos a la vez.
Usa un min-heap de tamaño 3. Mete cada puntuación. Cuando el heap tenga 4 elementos, saca uno. Como es un min-heap, el que tiras es el más pequeño. Al final, solo quedan los 3 mejores.
use std::cmp::Reverse;use std::collections::BinaryHeap;
fn main() { let scores = [72, 95, 64, 88, 91, 55, 99, 80]; let mut top = BinaryHeap::new(); // un min-heap: la puntuación más baja está arriba
for score in scores { top.push(Reverse(score)); if top.len() > 3 { top.pop(); // cuatro es uno de más: quita la más baja } }
let best: Vec<u32> = top .into_sorted_vec() .into_iter() .map(|Reverse(score)| score) .collect(); println!("las 3 mejores: {:?}", best);}Salida:
las 3 mejores: [99, 95, 91]Un buen extra: into_sorted_vec ordena los valores Reverse de “menor” a “mayor”, lo que para las puntuaciones reales significa de mayor a menor. Así que la mejor puntuación sale primero, sin ordenar nada más. Este patrón funciona con una lista de un millón de puntuaciones y nunca guarda más de 4 en memoria.
¿Cuál debería usar?
Sección titulada «¿Cuál debería usar?»Aquí tienes una chuleta. “Rápido” significa que el tiempo se mantiene más o menos igual por grande que sea la colección. “Lento” significa que crece con el tamaño.
| Colección | Ideal para | Meter | Sacar de delante / de arriba | Buscar un elemento cualquiera |
|---|---|---|---|---|
Vec<T> |
Una lista que lees por posición; una pila | Rápido al final | Lento (remove(0) lo mueve todo) |
Lento (mira cada elemento) |
VecDeque<T> |
Colas: añadir y quitar por los dos extremos | Rápido en los dos extremos | Rápido en los dos extremos | Lento (mira cada elemento) |
BinaryHeap<T> |
“Dame el más grande / el más urgente” | Bastante rápido (O(log n)) | Bastante rápido: siempre el más grande | Lento, y sin orden |
BTreeSet<T> |
Valores únicos, siempre ordenados | Bastante rápido (O(log n)) | Bastante rápido: el más pequeño y el más grande | Bastante rápido (O(log n)) |
Si dudas, empieza con un Vec. Pasa a un VecDeque cuando saques cosas por delante. Pasa a un BinaryHeap cuando te preguntes una y otra vez “¿qué es lo más importante ahora?”. Usa un BTreeSet cuando necesites valores únicos y ordenados y quieras buscar elementos, no solo sacar el de arriba.
Errores comunes
Sección titulada «Errores comunes»Esperar que .iter() en un BinaryHeap salga ordenado. Un heap solo está ordenado a medias. El elemento más grande va primero, pero el resto está en un orden revuelto que le sirve al heap, no a ti:
use std::collections::BinaryHeap;
fn main() { let heap = BinaryHeap::from(vec![1, 5, 2, 8, 3, 9]);
println!("iter: {:?}", heap.iter().collect::<Vec<_>>()); println!("ordenado: {:?}", heap.clone().into_sorted_vec());
let mut heap = heap; print!("sacados:"); while let Some(n) = heap.pop() { print!(" {n}"); } println!();}Salida:
iter: [9, 8, 2, 5, 3, 1]ordenado: [1, 2, 3, 5, 8, 9]sacados: 9 8 5 3 2 1Lo mismo pasa si imprimes un heap con {:?}: verás el desorden interno. Si necesitas los elementos en orden, sácalos uno a uno con pop o llama a into_sorted_vec.
Meter en un BinaryHeap un struct sin Ord. El heap tiene que comparar los elementos para saber cuál va arriba. Si tu struct no se puede comparar, Rust te detiene:
use std::collections::BinaryHeap;
struct Ticket { urgency: u8, title: String,}
fn main() { let mut tickets = BinaryHeap::new(); tickets.push(Ticket { urgency: 3, title: "el login no funciona".to_string() });}error[E0277]: the trait bound `Ticket: Ord` is not satisfied --> src/main.rs:10:13 |10 | tickets.push(Ticket { urgency: 3, title: "el login no funciona".to_string() }); | ^^^^ the trait `Ord` is not implemented for `Ticket` |help: consider annotating `Ticket` with `#[derive(Ord)]`El compilador dice que Ticket no implementa el trait Ord, y sugiere añadir #[derive(Ord)]. La solución es poner #[derive(PartialEq, Eq, PartialOrd, Ord)] encima del struct (Ord también necesita los otros tres), con urgency como primer campo. O escribir Ord a mano, como en el ejemplo de Job de más arriba.
Práctica
Sección titulada «Práctica»1. Por turnos
Sección titulada «1. Por turnos»Tres amigos, Ana, Ben y Cai, juegan por turnos a un juego de mesa. Mételos en un VecDeque e imprime quién juega en los turnos del 1 al 5. Después de cada turno, el jugador vuelve al final de la fila. Pista: saca al jugador con pop_front, imprímelo y vuelve a meterlo con push_back.
Ver solución
use std::collections::VecDeque;
fn main() { let mut players = VecDeque::from(vec!["Ana", "Ben", "Cai"]);
for turn in 1..=5 { let player = players.pop_front().unwrap(); println!("Turno {turn}: {player}"); players.push_back(player); // de vuelta al final de la fila }}Salida:
Turno 1: AnaTurno 2: BenTurno 3: CaiTurno 4: AnaTurno 5: BenEn un VecDeque, tanto el principio como el final son rápidos, así que rotar la fila no cuesta casi nada, haya los jugadores que haya.
2. Urgencias
Sección titulada «2. Urgencias»Los pacientes llegan con una gravedad del 1 al 5. El más grave pasa primero. Si dos pacientes tienen la misma gravedad, pasa primero el que llegó antes. Usa un BinaryHeap de tuplas e imprime el orden en que se atiende a los pacientes. Pacientes: Mia (gravedad 2, llegada 1), Leo (gravedad 5, llegada 2), Sam (gravedad 2, llegada 3) y Zoe (gravedad 4, llegada 4). Pista: las tuplas se comparan campo a campo, igual que un struct con derive. Prueba con (severity, Reverse(arrival), name).
Ver solución
use std::cmp::Reverse;use std::collections::BinaryHeap;
fn main() { // (gravedad, Reverse(llegada), nombre) let mut waiting = BinaryHeap::new(); waiting.push((2, Reverse(1), "Mia")); waiting.push((5, Reverse(2), "Leo")); waiting.push((2, Reverse(3), "Sam")); waiting.push((4, Reverse(4), "Zoe"));
while let Some((severity, Reverse(arrival), name)) = waiting.pop() { println!("{name} (gravedad {severity}, llegada #{arrival})"); }}Salida:
Leo (gravedad 5, llegada #2)Zoe (gravedad 4, llegada #4)Mia (gravedad 2, llegada #1)Sam (gravedad 2, llegada #3)La gravedad se compara primero, así que Leo pasa el primero. Mia y Sam empatan en gravedad, así que decide Reverse(arrival), y gana quien llegó antes (Mia). Como cada número de llegada es distinto, nunca hace falta el nombre para desempatar.
Resumen
Sección titulada «Resumen»- Un
Veces una mala cola:remove(0)mueve todos los demás elementos. UnVecDequees un ring buffer (búfer circular) conpush/poprápidos en los dos extremos. - Usa
VecDequepara colas en las que el primero en entrar es el primero en salir, historiales de “los últimos N elementos” y ventanas deslizantes. - Un
BinaryHeapes un max-heap:popypeeksiempre te dan el elemento más grande, sin ordenarlo todo. - Envuelve los valores en
std::cmp::Reversepara tener un min-heap (el más pequeño primero). - Los structs de un heap necesitan
Ord. Conderive, los campos se comparan de arriba abajo, así que pon la prioridad primero. EscribeOrda mano para desempates personalizados. .iter()en un heap no sale ordenado. Saca los elementos uno a uno conpopo usainto_sorted_vec.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Por qué VecDeque::pop_front es más rápido que Vec::remove(0) en una lista larga?
2. Metes 4, 9 y 2 en un BinaryHeap. ¿Qué devuelve el primer pop()?
3. ¿Cómo haces que un BinaryHeap te dé primero el valor más pequeño?
4. Un struct deriva Ord con los campos name y después priority. ¿Qué decide el orden en un BinaryHeap?
Puntuación: 0 / 4
Para leer más
Sección titulada «Para leer más»std::collections— documentación de la biblioteca estándar (en inglés): la guía oficial de “¿qué colección debería usar?”, con una tabla de velocidades para cada colección.VecDeque— documentación de la biblioteca estándar (en inglés): todos los métodos, incluidosrotate_left,range,retainyas_slices.BinaryHeap— documentación de la biblioteca estándar (en inglés): todos los métodos del heap, más ejemplos de min-heaps conReversey depeek_mut.Reverse— documentación de la biblioteca estándar (en inglés): el pequeño envoltorio que invierte cualquier comparación, con ejemplos de ordenación inversa.- Colecciones comunes — El libro de Rust (en inglés): el capítulo del libro sobre vectores, cadenas de texto y hash maps, las colecciones que usarás junto a estas.