Anuncios

Bienvenidos sean a este post, hoy hablaremos un poco sobre esta libreria.

Anuncios

Esta nos provee con un enfoque generico para componer y trabajar con colecciones de objetos. Su manera de trabajar es mediante iteradores que pasan atraves de los contenedores permitiendo que podemos trabajar con sus elementos. Los iteradores son herramientas que nos permiten tener un acoplamiento flexible entre algoritmos y contenedores.

Anuncios

Tomemos como ejemplo el codigo del post anterior, en este usamos a count_if para aplicarlo en el vector, pero a este no sabe a que contenedor se aplico. Veamos como es su declaracion:

template <typename InputIterator, typename UnaryPredicate>
constexpr typename iterator_traits<InputIterator>::difference_type
  count_if(InputIterator first, InputIterator last, UnaryPredicate p);
Anuncios

Observen que a pesar de su declaracion detallada, este no toma a un contenedor como un argumento sino que opera con iteradores, especialmente iteradores de entrada, sobre este concepto hemos hablado en posts anteriores. Esto permite a los algoritmos iterar sobre contenedores sin saber exactamente el tipo del mismo. Veamos el siguiente ejemplo:

#include <iostream>
#include <algorithm>
#include <array>

int main()
{
        std::array<int, 4> arr{1, 2, 3, 4};
        auto res = std::count_if(arr.cbegin(), arr.cend(),
                [](int x){ return x == 3; });
        std::cout << "Hay " << res << " elemento igual a 3\n";

        return 0;
}
Anuncios

En este ejemplo, mediante la libreria array generamos uno con cuatro elementos. Lo siguiente es una variable donde almacenaremos el resultado de contar cuando se cumpla la condicion, para ello pasaremos el rango donde trabajaremos y usaremos una funcion anonima donde devolvera un true solo si x es igual a 3. Una vez finalizado, mostraremos un mensaje con la cantidad de coincidencias. Compilemos y veamos como es la salida:

$ ./ranges
Hay 1 elemento igual a 3
$
Anuncios

Mas alla de su naturaleza generica, los algoritmos no se componen bien. Por lo general, aplicamos un algoritmo a una colección y almacenamos el resultado del algoritmo como otra colección que podemos aplicar a más algoritmos de la misma manera mucho mas adelante. Con esto comentado, hablemos de la libreria ranges y para ello analizaremos un ejemplo simple:

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
        std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
        auto incremento = [](int n) { return ++n; };
        for (int i : v
                | std::views::transform(incremento))
                std::cout << i << ' ';

        std::cout << '\n';
        return 0;
}
Anuncios
Anuncios

Este es nuestro primer ejemplo aplicando ranges. Aqui tenemos un vector simple con una serie de valores. Lo siguiente es una variable que contendra una funcion de lambda o anonima. En esta, recibiremos un valor y devolveremos el incremento del valor recibido. Luego tenemos un bucle donde pasaremos por todo el vector y a cada elemento obtenido del vector le aplicaremos la funcion transform y como argumento pasamos la variable con la funcion anteriormente comentada. Este primer adaptador de ranges nos permite tomar la vista que contiene los elementos y nos provee una vista del incremento de cada elemento. En el bucle mostramos el valor incrementado. Pero este codigo no se compilara como los demas, para ello deben ejecutar el compilador de la siguiente manera:

$ g++ --std=c++20 ranges.cpp -o ranges
Anuncios

Debemos agregar la opcion std para indicar que utilice el estandar C++20 porque de lo contrario no encontrara a la libreria ranges. Con nuestro codigo compilado, veamos como es la salida:

$ ./ranges
2 3 4 5 6 7 8 9 10
$
Anuncios
Anuncios

Tenemos cada valor incrementado del vector, esto gracias al view generado por transform. Cuando un adaptador de ranges produce un view, no incurre en el costo de transformar cada elemento en el rango para producir esa view. Este costo de procesar un elemento en el view es realizado cuando se accede a ese elemento. Esto es asi porque cuando creamos un view es para un trabajo a futuro. Si observamos el codigo anterior, la creacion del view no resulta en el incremento de cada elemento. El trabajo sucede solo cuando se accede al elemento en el view. Los elementos de un view son usualmente los elementos actuales del rango usado para crear el view. El view usualmente no se adueña de los elementos, sino simplemente los referencia. Cambiando un elemento cambia ese elemento en el rango del view desde donde fue creado. Tomemos el codigo anterior y hagamos el siguiente cambio:

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
        std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
        auto incremento = [](int n) { return ++n; };
        for (int i : v
                | std::views::transform(incremento)
                | std::views::filter([](int n) { return n % 2 == 0; }))
                std::cout << i << ' ';

        std::cout << '\n';
        return 0;
}
Anuncios

Este codigo es casi igual al que vimos anteriormente, la unica diferencia es la aplicacion de un nuevo adaptador de ranges. En este caso agregamos a filter para poder generar un view de filtrado al view generado por transform. En este caso, en particular solamente dejara pasar a los valores pares, los que el operador de modulo dividido por 2 devuelva el valor de 0. Compilemos y veamos como es la salida:

$ ./ranges
2 4 6 8 10
$
Anuncios

Se cumplio lo que comentamos anteriormente, donde no solamente sigue actuando el incremento sino que ahora tambien se muestran unicamente los valores pares del incremento. Si observan la modificacion, usamos nuevamente a pipe (|) para pasar a filter. Este posee una conducta similar al usado en Unix, donde cada nueva funcion trabajara con el resultado de la instruccion anterior. Tal como sucede en el codigo, donde el valor obtenido del bucle, se le aplica a transform, y a este se le aplica filter, y si se agregara otro se aplicaria a este.

Anuncios

Como comentamos anteriormente, tanto la funcion transform como filter generan su propio view, pero ellos no modifican o evaluan algo. Cuando asignamos el resultado a la coleccion de resultado, se construye desde el view a medida que se accede a cada elemento. Ahi es donde la evaluacion ocurre. Es tan simple como eso, ranges nos provee una composicion de funciones con evaluacion diferida.

Anuncios

En resumen, hoy hemos visto a ranges, que es, para que sirve, como nos ayuda con la programacion funcional, asi como unos ejemplos para ver algunas facilidades que nos agrega. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Donatión

It’s for site maintenance, thanks!

$1.50