- Vistas y acciones, ejemplos 1 (generate_n, group_by, sort, to) y 2 (filter)
- Ejemplos 3 (enumerate, shuffle, take, zip) y 4 (intersperse, tokenize)
- Ejemplos 5 (getlines, transform) y 6 (iota, set_difference, split_when)
- Ejemplo 7 (stable_partition, subrange)
- Ejemplo 8 (concat, drop, generate, take_while)
- Ejemplos 9 (cycle) y 10 (keys, values)
- Ejemplo 11 (cartesian_product)
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:
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:
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 5, 8 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 (5, 8, 9) 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:
Nuestro breve programa adoptaría, así, la forma siguiente:
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{3, 4, 6, 7, 10, 11, 12};
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