Algoritmos STL en C++ aplicados a sistemas de conducción autónoma

Estos algoritmos no alteran los elementos de los contenedores con los que operan.

1.1 find y find_if
  • find(inicio, fin, valor): Devuelve un iterador al primer elemento que es igual al valor especificado.
  • find_if(inicio, fin, predicado): Devuelve un iterador al primer elemento que satisface el predicado.
std::vector<double> lecturas_sensor = {10.5, 12.1, 8.7, 15.3, 9.2};

// Buscar la lectura de 12.1
auto it_sensor = std::find(lecturas_sensor.begin(), lecturas_sensor.end(), 12.1);
if (it_sensor != lecturas_sensor.end()) {
    std::cout << "Lectura encontrada: " << *it_sensor << std::endl;
}

// Buscar primera lectura superior a 14.0
auto it_alerta = std::find_if(lecturas_sensor.begin(), lecturas_sensor.end(), [](double valor) {
    return valor > 14.0;
});

1.2 count y count_if
  • count(inicio, fin, valor): Cuenta los elementos que son iguales al valor.
  • count_if(inicio, fin, predicado): Cuenta los elementos que cumplen el predicado.
std::vector<int> puntos_deteccion = {1, 0, 0, 1, 1, 0, 1};
int detecciones_positivas = std::count(puntos_deteccion.begin(), puntos_deteccion.end(), 1);
int umbrales_superados = std::count_if(puntos_deteccion.begin(), puntos_deteccion.end(), [](int val) {
    return val > 0;
});

1.3 for_each

Aplica una función a cada elemento dentro de un rango.

std::vector<double> distancias = {1.2, 0.8, 2.5, 3.1};
std::for_each(distancias.begin(), distancias.end(), [](double& d) {
    d *= 100.0; // Convertir metros a centímetros
});

1.4 equal y mismatch
  • equal(inicio1, fin1, inicio2): Comprueba si dos rangos son iguales.
  • mismatch(inicio1, fin1, inicio2): Devuelve un par de iteradores que apuntan al primer elemento diferente entre dos rangos.
std::vector<int> ruta_A = {1, 2, 3};
std::vector<int> ruta_B = {1, 2, 4};

bool rutas_coinciden = std::equal(ruta_A.begin(), ruta_A.end(), ruta_B.begin());
auto diferencia = std::mismatch(ruta_A.begin(), ruta_A.end(), ruta_B.begin());

1.5 all_of, any_of, none_of

Evalúan condiciones lógicas sobre todos los elementos de un rango.

std::vector<bool> sensores_activos = {true, true, false};
bool sistema_completo = std::all_of(sensores_activos.begin(), sensores_activos.end(), [](bool s){ return s; });
bool hay_sensor = std::any_of(sensores_activos.begin(), sensores_activos.end(), [](bool s){ return s; });

2. Algoritmos de secuencia modificadora

Estos algoritmos pueden cambiar los elementos dentro del contenedor.

2.1 copy y copy_if
  • copy(inicio, fin, destino): Copia los elementos del rango fuente al destino.
  • copy_if(inicio, fin, destino, predicado): Solo copia los elementos que cumplen el predicado.
std::vector<float> datos_gps_completos = {45.1, 45.2, 44.9, 45.3, 44.8};
std::vector<float> datos_validos;
std::copy_if(datos_gps_completos.begin(), datos_gps_completos.end(), std::back_inserter(datos_validos), [](float coord) {
    return (coord > 44.95) && (coord < 45.25);
});

2.2 transform

Aplica una operación a los elementos de un rango y almacena el resultado en otro.

std::vector<int> ids_objetos = {101, 102, 103};
std::vector<std::string> etiquetas;
std::transform(ids_objetos.begin(), ids_objetos.end(), std::back_inserter(etiquetas), [](int id) {
    return "OBJ_" + std::to_string(id);
});

2.3 replace, replace_if y replace_copy

Reemplazan valores específicos o que cumplen una condición, ya sea directamente o copiando a un nuevo contenedor.

std::vector<char> marca_temporal = {'A', 'B', 'A', 'C', 'A'};
// Reemplazar todas las 'A' por 'T' (Timestamp)
std::replace(marca_temporal.begin(), marca_temporal.end(), 'A', 'T');

2.4 remove, remove_if y erase

remove no elimina físicamente los elementos; los mueve al final del contenedor. Se debe usar junto con erase para reducir el tamaño.

std::vector<double> puntos_camino = {1.0, 0.0, 2.5, 0.0, 3.7};
auto nuevo_fin = std::remove(puntos_camino.begin(), puntos_camino.end(), 0.0);
puntos_camino.erase(nuevo_fin, puntos_camino.end()); // Elimina los ceros

2.5 unique

Elimina elementos consecutivos duplicados. Se suele usar después de ordenar.

std::vector<int> etiquetas_clase = {1, 2, 2, 3, 3, 3, 4};
auto final_unico = std::unique(etiquetas_clase.begin(), etiquetas_clase.end());
etiquetas_clase.erase(final_unico, etiquetas_clase.end());

2.6 reverse

Invierte el orden de los elementos en un rango.

std::vector<int> historial_errores = {1001, 1002, 1003};
std::reverse(historial_errores.begin(), historial_errores.end());

2.7 rotate

Realiza una rotación en el rango, moviendo un segmento al principio.

std::vector<std::string> secuencia_planificada = {"giro_izq", "avanzar", "detener", "giro_der"};
// Rotar para empezar por 'avanzar'
std::rotate(secuencia_planificada.begin(), secuencia_planificada.begin() + 1, secuencia_planificada.end());

3. Algoritmos de ordenación y relacionados

3.1 sort, stable_sort y partial_sort
  • sort: Ordenación rápida (introsort), no estable.
  • stable_sort: Ordenación estable (mantiene el orden relativo de elementos iguales).
  • partial_sort: Ordena solo una parte del rango.
struct ObjetoDetectado {
    float distancia;
    int id;
};

std::vector<ObjetoDetectado> objetos_cercanos = {{5.2, 1}, {3.1, 2}, {5.2, 3}, {2.8, 4}};
// Ordenar por distancia (estable para objetos a misma distancia)
std::stable_sort(objetos_cercanos.begin(), objetos_cercanos.end(), [](const ObjetoDetectado& a, const ObjetoDetectado& b) {
    return a.distancia < b.distancia;
});

3.2 nth_element

Reorganiza el rango para que el elemento en la n-ésima posición sea el que estaría allí en una secuencia completamente ordenada.

std::vector<float> tiempos_respuesta = {0.1, 0.5, 0.3, 0.2, 0.4};
// Encontrar la mediana (elemento en la posición 2)
std::nth_element(tiempos_respuesta.begin(), tiempos_respuesta.begin() + 2, tiempos_respuesta.end());
float mediana = tiempos_respuesta[2];

3.3 binary_search, lower_bound, upper_bound

Requieren un rango ordenado. Se usan para búsquedas eficientes O(log n).

std::vector<int> ids_canales_ordenados = {10, 20, 30, 40, 50};
bool canal_30_activo = std::binary_search(ids_canales_ordenados.begin(), ids_canales_ordenados.end(), 30);
auto iter_sup = std::upper_bound(ids_canales_ordenados.begin(), ids_canales_ordenados.end(), 25); // Apunta a 30

3.4 merge

Fusiona dos rangos ordenados en un solo rango ordenado.

std::vector<int> puntos_ruta_1 = {1, 3, 5};
std::vector<int> puntos_ruta_2 = {2, 4, 6};
std::vector<int> ruta_fusionada(puntos_ruta_1.size() + puntos_ruta_2.size());
std::merge(puntos_ruta_1.begin(), puntos_ruta_1.end(), puntos_ruta_2.begin(), puntos_ruta_2.end(), ruta_fusionada.begin());

4. Algoritmos de montículo (heap)

Permiten tratar un rango como un montículo (heap), útil para gestionar prioridades.

std::vector<int> prioridades_tareas = {3, 1, 4, 1, 5, 9};
std::make_heap(prioridades_tareas.begin(), prioridades_tareas.end()); // Convierte en un max-heap
int tarea_mas_prioritaria = prioridades_tareas.front();
std::pop_heap(prioridades_tareas.begin(), prioridades_tareas.end()); // Mueve el mayor al final
prioridades_tareas.pop_back(); // Elimina la tarea más prioritaria

5. Algoritmos de valor mínimo/máximo

5.1 min_element y max_element

Devuelven iteradores al elemento mínimo y máximo en un rango.

std::vector<double> intensidades_senal = {0.8, 0.3, 0.9, 0.1, 0.5};
auto senal_mas_fuerte = std::max_element(intensidades_senal.begin(), intensidades_senal.end());
auto senal_mas_debil = std::min_element(intensidades_senal.begin(), intensidades_senal.end());

6. Algoritmos numéricos (en <numeric>)

6.1 accumulate

Calcula la suma acumulada (u otra operación) de los elemantos.

#include <numeric>
std::vector<float> desplazamientos = {1.0f, -0.5f, 0.3f};
float desplazamiento_neto = std::accumulate(desplazamientos.begin(), desplazamientos.end(), 0.0f);

6.2 iota

Rellena un rango con valores consecutivos incrementando a partir de un valor inicial.

std::vector<int> ids_secuenciales(10);
std::iota(ids_secuenciales.begin(), ids_secuenciales.end(), 1000); // IDs desde 1000

6.3 partial_sum

Calcula la suma parcial (acumulada) y la almacena en otro rango.

std::vector<float> velocidades_por_segundo = {10.0f, 12.0f, 11.0f, 13.0f};
std::vector<float> distancia_acumulada(velocidades_por_segundo.size());
std::partial_sum(velocidades_por_segundo.begin(), velocidades_por_segundo.end(), distancia_acumulada.begin());

7. Algoritmos de conjunto

Operan en rangos ordendaos para realizar operaciones teóricas de conjuntos.

std::vector<std::string> caracteristicas_lidar = {"punto", "nube", "reflejo"};
std::vector<std::string> caracteristicas_camara = {"nube", "pixel", "color"};
std::vector<std::string> caracteristicas_comunes;
std::set_intersection(caracteristicas_lidar.begin(), caracteristicas_lidar.end(),
                     caracteristicas_camara.begin(), caracteristicas_camara.end(),
                     std::back_inserter(caracteristicas_comunes));
// caracteristicas_comunes contendrá "nube"

8. Preguntas frecuentes

¿Cuándo usar stable_sort en lugar de sort?
Se prefiere stable_sort cuando se necesita mantener el orden relativo de elementos que se consideran iguales según el criterio de ordenación. Por ejemplo, al ordenar una lista de detecciones por timestamp y luego por prioridad, un stable_sort secundario por prioridad no desordenará las detecciones con la misma prioridad que ya estaban ordenadas por tiempo.

¿Por qué la combinación erase + remove es un idiom?
El algoritmo remove opera por sobrescritura, moviendo los elementos no eliminados al inicio y devolviendo un iterador al nuevo final lógico. Sin embargo, el tamaño del contenedor no cambia. La llamada a erase con ese nuevo final y el final real del contenedor es la que ajusta físicamente el tamaño y libera los recursos de los elementos "lógicamente" eliminados.

¿Qué algoritmos requieren contenedores ordenados?
La familia de búsqueda binaria (binary_search, lower_bound, upper_bound), los algoritmos de conjuntos (set_union, set_intersection, etc.) y merge dependen de que los rangos de entrada estén ordenados para funcionar correctamente y con eficiencia O(n log n) o mejor.

Etiquetas: C++ STL algoritmos contenedores programación genérica

Publicado el 7-11 21:00