Algoritmos estándar de C++ para el preprocesamiento de datos en IoT

Introducción

En el desarrollo de aplicaciones de Internet de las Cosas (IoT), el manejo eficiente de los flujos de datos provenientes de sensores es crucial. La biblioteca estándar de plantillas (STL) de C++ ofrece un conjunto robusto de algoritmos genéricos que son ideales para operaciones comunes de transformación, filtrado y análisis de datos. Este artículo explora cómo aplicar estos algoritmos clave para el preprocesamiento de datos en escenarios típicos de IoT.

  1. Algoritmos de consulta sin modificación

Estos algoritmos inspeccionan los datos sin alterarlos, siendo útiles para la detección de patrones, validación y extracción de información básica.

1.1 Búsqueda de valores y patrones

Para localizar elementos específicos o que cumplan condiciones dentro de un vector de datos.

#include <algorithm>
#include <vector>
#include <iostream>

// Datos simulados de sensores de temperatura
std::vector<double> lecturas_temperatura = {22.5, 23.1, 18.7, 25.0, 19.3, 22.5};

// Buscar la primera lectura igual a 22.5
auto it = std::find(lecturas_temperatura.begin(), lecturas_temperatura.end(), 22.5);
if (it != lecturas_temperatura.end()) {
    std::cout << "Lectura 22.5 encontrada en el índice: " 
              << std::distance(lecturas_temperatura.begin(), it) << std::endl;
}

// Buscar la primera lectura por encima de un umbral (ej. 24.0)
auto it_umbral = std::find_if(lecturas_temperatura.begin(), lecturas_temperatura.end(),
    [](double temp) { return temp > 24.0; });
if (it_umbral != lecturas_temperatura.end()) {
    std::cout << "Primera lectura anormal: " << *it_umbral << std::endl;
}

1.2 Conteo de ocurrencias

Para cuantificar eventos específicos, como lecturas fuera de rango.

std::vector<int> niveles_humedad = {45, 62, 78, 95, 50, 92, 88};

// Contar cuántas lecturas exceden un nivel crítico (ej. 90)
int contador_critico = std::count_if(niveles_humedad.begin(), niveles_humedad.end(),
    [](int nivel) { return nivel > 90; });
std::cout << "Lecturas críticas de humedad: " << contador_critico << std::endl; // Salida: 2

1.3 Validación y comparación

Para verificar la integridad de los datos o comparar dos conjuntos de mediciones.

std::vector<int> valores_calibrados = {100, 200, 300, 400};
std::vector<int> valores_actuales = {100, 200, 350, 400};

// Verificar si todos los valores actuales están dentro de un margen del 10% de los calibrados
bool dentro_margen = std::equal(valores_actuales.begin(), valores_actuales.end(),
    valores_calibrados.begin(),
    [](int actual, int calibrado) {
        double tolerancia = 0.1 * calibrado;
        return std::abs(actual - calibrado) <= tolerancia;
    });
std::cout << "¿Lecturas dentro de tolerancia? " << (dentro_margen ? "Sí" : "No") << std::endl;

  1. Algoritmos de transformación de secuencias

Estos algoritmos modifican los datos, realizando tareas como limpieza, normalización o cálculo de métricas derivadas.

2.1 Transformación y mapeo

Para aplicar una función a cada elemento, útil para conversiones de unidades o cálculos.

#include <numeric>

std::vector<double> corrientes_mA = {120.5, 85.2, 200.1, 150.8};
std::vector<double> potencias_W(corrientes_mA.size());
const double voltaje = 12.0;

// Calcular potencia (P = V * I) para cada lectura
std::transform(corrientes_mA.begin(), corrientes_mA.end(), potencias_W.begin(),
    [voltaje](double corriente) { return voltaje * (corriente / 1000.0); });

// Calcular la potencia total consumida usando 'accumulate'
double potencia_total = std::accumulate(potencias_W.begin(), potencias_W.end(), 0.0);

2.2 Filtrado y remoción

Para eliminar datos no deseados o corruptos de la secuencia.

std::vector<double> valores_crudos = {1.02, -0.5, 1.05, 1.01, 100.0, 0.98};
// Eliminar valores negativos y aquellos claramente erróneos (ej. > 50)
valores_crudos.erase(
    std::remove_if(valores_crudos.begin(), valores_crudos.end(),
        [](double v) { return v < 0 || v > 50; }),
    valores_crudos.end()
);
// Ahora 'valores_crudos' solo contiene {1.02, 1.05, 1.01, 0.98}

2.3 Ordenamiento y búsqueda eficiente

Para organizar datos y realizar búsquedas rápidas después de la adquisición.

#include <algorithm>

std::vector<int> ids_dispositivos = {502, 104, 301, 207, 104, 502};

// Primero, ordenar y eliminar duplicados para tener una lista única
std::sort(ids_dispositivos.begin(), ids_dispositivos.end());
auto fin_unicos = std::unique(ids_dispositivos.begin(), ids_dispositivos.end());
ids_dispositivos.erase(fin_unicos, ids_dispositivos.end());
// ids_dispositivos ahora es {104, 207, 301, 502}

// Luego, realizar una búsqueda binaria para comprobar existencia rápida
int id_buscado = 207;
bool existe = std::binary_search(ids_dispositivos.begin(), ids_dispositivos.end(), id_buscado);

  1. Algoritmos numéricos

Funciones de la cabecera <numeric> específicas para cálculos matemáticos comunes en series temporales de sensores.

3.1 Acumulación y producto interior

#include <numeric>

std::vector<double> muestras_vibracion = {0.1, 0.3, 0.2, 0.5, 0.4};

// Calcular la energía de la vibración (suma de cuadrados)
double energia = std::inner_product(muestras_vibracion.begin(), muestras_vibracion.end(),
    muestras_vibracion.begin(), 0.0);

// Calcular la media móvil usando 'partial_sum' y división
std::vector<double> suma_movil(muestras_vibracion.size());
std::partial_sum(muestras_vibracion.begin(), muestras_vibracion.end(), suma_movil.begin());
// Para obtener la media móvil de ventana 3, se puede usar (suma_movil[i] - suma_movil[i-3]) / 3.0

3.2 Generación de secuencias

Para inicializar buffers de datos o crear índices temporales.

#include <numeric>

// Generar marcas de tiempo (timestamps) en intervalos de 5 segundos
std::vector<unsigned long> timestamps(10);
unsigned long inicio = 1672531200; // Ejemplo de epoch
std::iota(timestamps.begin(), timestamps.end(), inicio);
// Ahora timestamps contiene {inicio, inicio+1, inicio+2, ...} (representando segundos)

Consideraciones de rendimiento

Al trabajar con datos de IoT que pueden llegar en tiempo real, es importante considerar la complejidad algerítmica. Los algoritmos de ordenamiento como std::sort son O(n log n), mientras que búsquedas lineales con std::find son O(n). Para conjuntos de datos estáticos y ordenados, std::binary_search ofrece búsquedas O(log n). La elección del algoritmo adecuado impacta directamente la eficiencia del sistema embebido.

Etiquetas: C++ STL algoritmos preprocesamiento de datos IoT

Publicado el 6-16 19:04