Como primer ejemplo, consideremos el siguiente generador de números enteros pertenecientes a la sucesión de Fibonacci, es decir, aquélla que comienza con los números 0 y 1 y para la que cada valor subsiguiente se calcula como suma de los dos elementos que lo preceden. Por sencillez, ignoraremos el inevitable desbordamiento de enteros para valores elevados de la sucesión [1]:
El generador permite obtener los valores de la sucesión uno a uno por iteración --típicamente a través de un bucle for basado en rango-- sin necesidad de almacenarlos en un array o en un vector. El empleo en la corrutina de la palabra clave co_yield nos permite suspender su ejecución retornando el último valor calculado de la sucesión para, una vez procesado, poder retomar la función y calcular el siguiente número. El rango es formalmente infinito, por lo que resulta necesario adaptarlo con el fin de generar un conjunto finito de valores. Así, el siguiente bloque de código imprimiría en la terminal los siete primeros valores de la sucesión:
Resaltemos el hecho, una vez más, de que el rango adaptado fibonacci_gen()|std::views::take(7) no contiene los siete valores numéricos a imprimir (como sí lo haría un vector std::vector<int>), sino que los calcula sucesivamente conforme el bucle itera el rango.
A modo de segundo ejemplo, si quisiéramos obtener la suma de los valores octavo a décimo de la sucesión de Fibonacci (i.e., 13 + 21 + 34 = 68), bastaría adaptar el rango adecuadamente y acumular los enteros en la forma [1]:
El algoritmo std::ranges::fold_left de C++23 [2], que recibe como argumento el rango fib_rng = [first,last), es en este caso el encargado de generar los valores a sumar mediante sucesivos incrementos y desreferencias del iterador first. Nuevamente, no es necesario que los valores a sumar convivan simultáneamente en memoria en ningún momento.
Como tercer y último ejemplo, consideremos un flujo de entrada std::istream a un fichero JSON Lines [3]. De querer parsear cada una de sus líneas a través de la biblioteca nlohmann/json [4] y construir con ellas objetos de tipo T (que asumiremos inicializable por defecto), podríamos codificar el siguiente generador:
Observemos la definición del concepto constructible_from_json con el fin de garantizar en tiempo de compilación que el tipo T sea inicializable por defecto, así como que el programador haya implementado adecuadamente una función from_json asociada a T que, tal y como requiere la biblioteca, sea capaz de transformar un objeto nlohmann::json en un objeto T (la biblioteca permite también el uso de tipos move-only, aspecto éste que no discutiremos en este artículo).
En un post anterior dedicado a la vista std::views::chunk_by procedimos a deserializar la totalidad de líneas de un fichero JSON Lines con el fin de almacenar la información disponible (nombre, país y año de victoria) de los campeones del individual masculino de Roland Garros - French Open dentro de objetos de tipo Champion:
En dicho artículo, hicimos un uso combinado de las vistas tl::views::getlines (no estandarizada) y std::views::transform para parsear las líneas JSON y almacenarlas en un vector de objetos Champion:
Gracias al parseador genérico from_json_lines, podemos ahora obtener dicho vector, sin recurrir a bibliotecas de terceros, en la siguiente forma alternativa:
O bien, integrando el establecimiento del flujo de entrada con el fichero en la propia función from_json_lines:
El lector puede encontrar una implementación experimental de std::generator en la referencia [5], válida para los principales compiladores Clang, GCC y MSVC.
- P2502R2 - std::generator: Synchronous Coroutine Generator for Ranges - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf
- P2322R6 - ranges::fold - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2322r6.html
- JSON Lines - Documentation - https://jsonlines.org/
- nlohmann/json - https://github.com/nlohmann/json
- Casey Carter, Lewis Baker, Corentin Jabot - std::generator implementation - https://godbolt.org/z/5hcaPcfvP
No hay comentarios:
Publicar un comentario