C++ Lambda expressions 2/2 C++ moderno con algoritmos de la STL

C++ Lambda expressions 2/2 C++ moderno con algoritmos de la STL

C++ Lambda expressions 2/2

Esta es la continuación a un primero post sobre las expresiones lambda. Si has llegado aquí sin aun verlo, te recomiendo con mucha prisa que leas la teoría y ejemplos que allí se explican. Puedes ir a la publicación dando click a este enlace https://thewhitecode.com/blog/8030


Las expresiones lambda son un concepto bastante sencillo que ha venido implementado desde la versión C++11, sin embargo, debes usarlas con cautela para hacer tu código más entendible posible, pero no hacer de este un laberinto.


Las lambdas han venido para enriquecer la experiencia del programador al usar contenedores, algoritmos y demás de esta biblioteca STL. En esta segunda parte nos enfocaremos en los algoritmos de la biblioteca STL y de como sacarles provecho usando las expresiones lambda.


Aunque queremos encofrarnos en los algoritmos de la biblioteca STL, primero veremos un ejemplo de la vida real, en donde entenderemos el tremendo trabajo que nos puede ahorrar una lambda.


En este ejemplo queremos encontrar si todos los valores de un vector están comprendidos exactamente en un rango dado.


Para ello usaremos un vector en donde guardar la secuencia de números, una función la cual imprimirá en pantalla los valores que se encuentran en el vector y una clase template, la cual hará de functor para la función std::all_of.  Y con esta última, buscaremos si cada uno de los objetos están en el rango dado.


Además de esto, usaremos la función std::iota, la cual aplicada a un vector, lo llenara secuencialmente partiendo del valor del parámetro dado.


Reccoriendo el vector con ayuda de std::for_each

Lo primero que haremos es escribir nuestra función print_vector, la cual imprimirá los valores en el vector.


1
2
3
4
5
6
void print_vector(std::vector<int> & vec){
    std::for_each(vec.begin(),vec.end(),[](int val){ 
         std::cout << val << ", ";
    });
    std::cout<<std::endl;
}

La primera función que explicaremos es std::for_each(…) . Esta aplicara una acción por medio de una función o predicado para cada elemento del vector. Con std::for_each no es posible modificar valores del contenedor. Y es exactamente aquí donde viene el uso de una expresión lambda, la cual imprime el valor actual.


std::for_each(…) Iterará por todo el vector sin necesidad de un bucle y llamará a la expresión lambda cada vez, de modo que el valor actual será dado a la expresión lambda como parámetro. Es por eso por lo que es posible imprimir de esta manera el vector entero.


El functor se verá de la siguiente forma:


1
2
3
4
5
6
7
8
9
class IsBetween
{
public:
    IsBetween(int a, int b) : a_(a), b_(b) {}
    bool operator()(int x) { return a_ <= x && x <= b_; }
private:
    int a_;
    int b_;
};

Lo importante aquí es la definición del operador ( ), el cual será invocado por la función std::all_of y  actuará como predicado para evaluar si el valor recibido esta en el rango dado.


Llenando el vector con ayuda de std::iota

Para testear usamos la función std::iota, la cual llenará de valores el vector secuencialmente a partir del valor -4.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(){
    std::vector<int> numbers(10);
    std::iota(numbers.begin(), numbers.end(), -4);

    print_vector(numbers);

    bool allBetweenAandB = std::all_of(numbers.begin(), numbers.end(), IsBetween(-4, 5));

    std::cout<<allBetweenAandB;
}

El resultado es exitoso con el siguiente output


El mismo resultado con mucho menos código y de una manera mas elegante lo podemos implementar haciendo uso de una expresión lambda de la siguiente manera:


1
2
3
4
5
int main(){ 
      int a = -4,b = 5;
      bool allBetweenAandB_lambda = std::all_of(numbers.begin(), numbers.end(),
            [a,b](int x) { return a <= x && x <= b; });
}

Como vemos se puede reducir bastante el codigo al usar expresiones lambda.


Algoritmos genericos de la Biblioteca STL

A continuación, veremos una lista de algoritmos genéricos de la biblioteca STL, los cuales abarcaremos para entender el posible uso de expresiones lambda.


std::all_of(...) , std::any_of (...), std::none_of(...)

Estas funciones pueden ser usadas para evaluar si en un vector todos los valores, alguno o ninguno cumplen con una determinada condición.


En el caso de std::all_of, se retornará true si el predicado aplica para todos los elementos en el rango y también en caso de que el rango este vacío.


std::any_of retorna true si el predicado aplica (es valido) para por lo menos un elemento en el rango, de otro modo retorna false. En caso de el rango estar vacío, este retornara igualmente false.


std::none_of retornara true si ninguno de los elementos en el vector cumple con la condición dada o si el vector está vacío, de otro modo false


Todas estas funciones toman como parámetro un predicado (UnaryPredicate), es allí donde usaremos nuestra expresión lambda.


El siguiente codigo ejemplifica como detectar numeros pares e impares en un rango dado:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void _all_any_none_of(){
              
    // Llenamos el vector con el numero 2, repetido 10 veces
    std::vector<int> v(10, 2);
    std::partial_sum(v.cbegin(), v.cend(), v.begin());
    std::cout << "Among the numbers: ";

    // Una manera elegante de imprimir los valores del vector: 
    std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\n';

    if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; } )) {
        std::cout << "All numbers are even\n";
    }
    if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(), 
                                                     std::placeholders::_1, 2))) {
        std::cout << "None of them are odd\n";
    }
    
    // Alternativa a no user una lambda:
    struct DivisibleBy
    {
        const int d;
        DivisibleBy(int n) : d(n) {}
        bool operator()(int n) const { return n % d == 0; }
    };
 
    if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7))) {
        std::cout << "At least one number is divisible by 7\n";
    }

    // Alternativa con lambda:
    if (std::any_of(v.cbegin(), v.cend(), [](int n){return n % 7 == 0;})) {
        std::cout << "At least one number is divisible by 7\n";
    }
}

el vector v lo estamos llenando con el numero 2 repetido 10 veces, osea 10 numeros 2 estan en el.


La funcion std::partial_sum calcula la suma parcial de los elementos en los subrangos o vectores dados ( en este caso solo los valores del vector v) y practicamente convierte la secuencia de 2, 2, 2, 2, 2, 2,2, 2, 2, 2 en 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 


Usando la funcion std::copy imprimimos de una manera muy elegante los valores del vector, al pasarle una copia al iterador std::ostream_iterator.


Output en la consola

std::for_each()

Esta funcion aplica una funcion o predicado a un rango dado.


En este ejemplo usamos la funcion std::for_each() para recorrer el vector y con ayuda de la lambda declarada en la variable print.


Aunque la funcion std::for_each no cambia valores del vector por si misma, es posible cambiar cada valor al capturarlo por referencia, lo cual sucede en la linea 17 con la expresion [ ](int &n){ n++; }.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void _for_each(){
    
    struct Sum
    {
        void operator()(int n) { sum += n; }
        int sum{0};
    };

    std::vector<int> nums{3, 4, 2, 8, 15, 267};
 
    auto print = [](const int& n) { std::cout << " " << n; };
 
    std::cout << "before:";
    std::for_each(nums.cbegin(), nums.cend(), print);
    std::cout << '\n';
 
    std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
 
    // calls Sum::operator() for each number
    Sum s = std::for_each(nums.begin(), nums.end(), Sum());
 
    std::cout << "after: ";
    std::for_each(nums.cbegin(), nums.cend(), print);
    std::cout << '\n';
    std::cout << "sum: " << s.sum << '\n';
}

Las funciones genericas de la biblioteca STL tambien pueden usarse por medio de una clase, la cual tenga definido el operador ( ). Es por ello que usamos la estructura Sum , la cual por medio del operador ( ) agrega a la suma el valor de cada elemento.


Sanchez

Profesional en informatica medica con enfasis en algoritmos y analizis de imagen en C++. Programador con experiencia en C/C++ y Python.

Reactions

1

0

0

0

Responsive image
Master

2

0

22-01-11 23:01

Muy buena contribucion!


Access hereTo be able to comment

TheWhiteCode.com is not the creator or owner of the images shown, references are: