Range-v3: Una introducción a la biblioteca (Parte III)


En este post proporcionaremos dos nuevos ejemplos de programación con la biblioteca Range-v3, un subconjunto de la cual ha sido adoptada por el nuevo estándar del lenguaje C++20. Pueden consultarse los espacios de nombres utilizados por este artículo en el primer post de la serie.


Ejemplo 5: getlines, transform


Consideremos un fichero JSON Lines data.jsonl en el que cada una de sus líneas

{"dat_1" : value_1, "data_2" : value_2, ... , "data_n" : value_n}

proporciona n parejas clave-valor "dat_i" : value_i con los que construir un agregado de datos Entry:

   struct Entry {       Type_1 dat_1;       // ...       Type_n dat_n;    };

Supongamos que deseamos almacenar la información en un vector entries de objetos de este tipo. De hacer uso de la biblioteca JSON for Modern C++ (https://github.com/nlohmann/json) para el parseo de las líneas, procederíamos a definir la siguiente función de deserealización from_json dentro del mismo espacio de nombres al que pertenezca Entry:

   using nlohmann::json;    void from_json(json const& j, Entry& e) {       j.at("dat_1").get_to(e.dat_1);       // ...       j.at("dat_n").get_to(e.dat_n);    }

El código que construye el vector podría adoptar entonces la forma simplificada siguiente:

   auto const entries = /* IILE */ []()-> vector<Entry> {       auto const file_name = "data.jsonl";       auto ifs = ifstream{file_name, ios::binary};       if (!ifs) throw ios_base::failure{fmt::format("unable to open '{}'", file_name)};       return rn::getlines(ifs) // (A)       | rv::transform([](string const& ln){ return json::parse(ln).get<Entry>(); })    | rn::to<vector>; // (B)    }();

La función getlines() del fichero de cabecera <range/v3/view/getlines.hpp> (A), incluido en <range/v3/core.hpp>, genera un rango de objetos std::string equivalente al que obtendríamos al aplicar sucesivamente la función estándar std::getline() sobre un flujo de entrada std::istream utilizando un carácter delimitador determinado ('\n' por defecto).

La vista intermedia transform retorna un nuevo rango en el que cada elemento es el resultado de parsear una línea JSON y generar con ella un objeto Entry. Este último rango de objetos es finalmente materializado en un vector mediante una operación to<> (B).


Ejemplo 6: iota, set_difference, split_when


Dada una secuencia estrictamente creciente de números enteros, por ejemplo {3,4,6,7,10,11,12}, ¿cómo podríamos determinar los subrangos de elementos consecutivos (es decir, que difieran sólo en una unidad)? En la lista anterior, dada la ausencia de los números 58 y 9, tales subrangos serían [3,4], [6,7] y [10,11,12].

Una solución particularmente sencilla consiste en generar una secuencia completa desde el mínimo valor anterior (3) hasta el mayor (12) y comparar ésta con la original para determinar los valores ausentes:

  Completa: 3 4 5 6 7 8 9 10 11 12

  Original: 3 4   6 7     10 11 12

A continuación, podríamos utilizar dichos valores ausentes (589) como delimitadores en la secuencia completa para, a través de un split, obtener los subrangos requeridos.

En primer lugar, incluyamos, junto a <range/v3/core.hpp>, los siguientes ficheros de cabecera:

   #include <range/v3/algorithm/contains.hpp>    #include <range/v3/view/iota.hpp>    #include <range/v3/view/set_algorithm.hpp>    #include <range/v3/view/split_when.hpp>

Nuestro breve programa adoptaría, así, la forma siguiente:

   auto const nums = array{3467101112};    auto whole_seq = rv::iota(nums.front(), nums.back() + 1);  // (A)    auto diff = rv::set_difference(whole_seq, nums);                // (B)    for (     auto is_delimiter = [&diff](int n){ return rn::contains(diff, n); };     auto subseq : whole_seq | rv::split_when(is_delimiter)     // (C) ) {       fmt::print("[{}] ", fmt::join(subseq, ","));    } // output: [3,4] [6,7] [10,11,12]

Aquí, la vista iota es la encargada de generar la secuencia completa whole_seq con los valores {3,4,5,6,7,8,9,10,11,12} (A). La vista set_difference, por su parte, determina los valores de diferencia {5,8,9} entre dicha secuencia y la original nums (B). La lista completa whole_seq es entonces sometida a una vista split con {5,8,9} como delimitadores (C), permitiendo al bucle la iteración a través de los subrangos resultantes [3,4][6,7] y [10,11,12]. El output mostrado por la terminal es, efectivamente, el esperado.


Pueden encontrarse los siguientes ejemplos de empleo de Range-v3 en el cuarto post de esta serie.

No hay comentarios:

Publicar un comentario