Skip to content
Snippets Groups Projects
Commit 3138d389 authored by Sandipan Mohanty's avatar Sandipan Mohanty
Browse files

lambda practice typos fixed

parent 18df6e1f
No related branches found
No related tags found
No related merge requests found
%% Cell type:code id:5b454048 tags:
``` C++
``` c++
#include <iostream>
#include <algorithm>
#include <vector>
#include <array>
#include <cmath>
#include <complex>
```
%% Cell type:markdown id:9541e5f1 tags:
# Practice using lambdas with algorithms
%% Cell type:markdown id:eb5b5448 tags:
## std::for_each
%% Cell type:markdown id:4d3f33ef tags:
In the following cell, we scale the elements of a vector by $1.5$ and print them. In a separate cell, use the algorithm `std::for_each` along with an appropriate lambda expression replace the values by the $sin(value)$, and
show the results.
%% Cell type:code id:99bbcf10 tags:
``` C++
``` c++
{
std::vector X{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.8, 0.7};
std::for_each(X.begin(), X.end(), [](auto& elem){ elem *= 1.5; });
std::for_each(X.begin(), X.end(), [](auto&& elem){ std::cout << elem << "\n"; });
}
```
%% Cell type:code id:fd0e92e9 tags:
``` C++
``` c++
```
%% Cell type:markdown id:c2172ca4 tags:
## std::transform
Using the slides as a guide, fill in the necessary code, so that the second sequence consists of the values $a x^2 + b x + c$, for $x$ taken from the first sequence, $a$ and $b$ being constants.
%% Cell type:code id:8fb70ac5 tags:
``` C++
``` c++
{
std::vector X{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.8, 0.7};
std::vector<double> Y;
const auto a = 2.7182818284590;
const auto b = 3.1415926535897;
// std::transform(what to what using what data transformation ?);
}
```
%% Cell type:markdown id:7f0597d6 tags:
## std::copy_if
Using the slides as a guild, write the necessary code to "filter" into the vector `Y` only those values out of the input vector `X`, for which the the cylindrical Bessel function of the first kind, $J_0(x) > 0.9$. Cylindrical Bessel function of the first kind $J_0(x)$ are available in the C++ standard library as `std::cyl_bessel_j(int, double)`.
%% Cell type:code id:07defab8 tags:
``` C++
``` c++
{
std::vector X{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.8, 0.7};
std::vector<double> Y;
}
```
%% Cell type:markdown id:fc56123b tags:
## std::partition
The algorithm `std::partition` takes
- the bounds of one sequence (as start and end iterators)
- a unary predicate callable entity (something that can be called with one parameter, and returns true or false)
and reorders the elements in the range so that all the elements which test true come before all elements which test false. The return value of the function is an iterator marking the partition point:
```c++
auto p = std::partition(start, stop, criterion);
```
The range `start` to `p` will then contain all elements which "pass" and `p` to `stop` will contain all elements which don't pass. Partition the range in the previous exercise using the $J_0(x)$ function and the selection criteria we used. Print the two sub ranges, along with the values of the Bessel function to show that `partition` did what is expected of it.
%% Cell type:code id:164bd972 tags:
``` C++
``` c++
{
std::vector X{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.8, 0.7};
auto p = std::partition(X.begin(), X.end(), [](auto x){ return std::cyl_bessel_j(0, x) > 0.9; });
std::cout << "Passed...\n";
std::for_each(X.begin(), p, [](auto&& x){ std::cout << x << "\t" << std::cyl_bessel_j(0, x) << "\n"; });
std::cout << "Did not pass.\n";
std::for_each(p, X.end(), [](auto&& x){ std::cout << x << "\t" << std::cyl_bessel_j(0, x) << "\n"; });
}
```
%% Cell type:markdown id:c9467bb9 tags:
## std::sort
Any container containing elements for which a "less than" comparison $x_i < x_j$ makes sense can be sorted. For instance, for integers, real numbers, strings etc. $a < b$ makes sense. But for complex numbers, for instance, such a comparison can not be uniquely defined. `std::sort(begin, end)` will sort any container in increasing order, so long as the elements can be compared to determine their relative ordering. Test:
%% Cell type:code id:cfcc07df tags:
``` C++
``` c++
{
std::vector X{1,5,3,8,9,2,7,2,6,3,1,0,8,4};
std::sort(X.begin(), X.end());
for (auto elem : X) std::cout << elem << "\n";
}
```
%% Cell type:code id:fc3ffce8 tags:
``` C++
``` c++
{
std::vector<std::string> X{"Conditionally", "copies", "elements", "from", "a", "source", "sequence", "to",
"a", "destination", "sequence"};
// Fun exercise for those who feel adventurous: Try using CTAD in defining this vector,
// and explain the results. What would you have to do to correctly use CTAD in this case ?
std::sort(X.begin(), X.end());
for (auto elem : X) std::cout << elem << "\n";
}
```
%% Cell type:code id:9a391f89 tags:
``` C++
``` c++
{
// But this does not work. If you run this, you may have restart the kernel!
std::vector<std::complex<double>> X{{1., 3.}, {0.99, 3.14}, {1.22, 0.12}};
std::sort(X.begin(), X.end(), [](auto x, auto y){ return std::abs(x) < std::abs(y); });
for (auto&& elem : X) std::cout << elem << "\n";
}
```
%% Cell type:markdown id:6f7badd2 tags:
However, `std::sort` can accept a callable object as another parameter, after the range parameters, which specifies how the contents of the range should be compared. It should be a function which takes two objects of the value type of the container, $x$ and $y$ (for instance), and return true if $x$ should be placed before $y$ for the current sorting operation. For instance, we can say that complex numbers should be sorted in such a way that the numbers with larger absolute value follow those with smaller absolute value. This can be expressed as $\lambda(x,y) = |x| < |y|$. Write the appropriate lambda expression as the 3rd argument to sort above and verify that you can sort by this criterion. Absolute values of complex number `c` can be calculated as `std::abs(c)`. Complex numbers are known to `std::cout`.
%% Cell type:markdown id:661c8dd9 tags:
How would you now sort an array of complex numbers by their real parts ? (Principle of least surprises applies here: `std::real(c)`, `std::imag(c)` ...)
%% Cell type:code id:881f5069 tags:
``` C++
``` c++
```
......
%% Cell type:code id:e7b04ecc tags:
``` C++
#pragma cling add_include_path("/p/project/training2213/local/include")
``` c++
#pragma cling add_include_path("/p/project/training2312/local/include")
#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/type_index.hpp>
#include "Vbose.hh"
template <class T>
void typeof(T&& t) {
std::cout << "Type name: " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << "\n";
}
```
%% Output
%% Cell type:markdown id:9d7b6595 tags:
# Lambda functions: captures
## When
Variables which can be used inside lambda functions are those declared inside the lambda function, its formal arguments, global variables and variables captured by the capture brackets. Variables captured with `=` captures are copied into the lambda function. When does the copy take place ? Immediately when the lambda is declared or at the point when it is used ? How often does the copy happen, if we declare it once, but use it in a loop two million times ? Does `[=]` capture copy every variable defined in the context of the lambda function in to the lambda ? We will use the `Vbose` class to explore...
%% Cell type:code id:1e7328f5 tags:
``` C++
``` c++
{
Vbose locvar{ "dinosaur" }, anothervar{"fish"};
std::cout << "Declaring lambda function {\n";
auto lambda = [=](int i) {
std::cout << "\nIn lambda function, captured value of locvar is : "
<< locvar.getval() << "\n";
return i * i * i;
};
std::cout << "Declaring lambda function }\n";
locvar.value("bird");
for (int i = 0; i < 5; ++i) {
std::cout << "Calling lambda function {\n";
std::cout << 5 << " -> " << lambda(5) << "\n";
std::cout << "Calling lambda function }\n";
}
}
```
%% Output
Constructor of object at 140514652397128, using string "dinosaur"
Constructor of object at 140514652397040, using string "fish"
Declaring lambda function {
Copy constructor of object at 140514652396968. Source for copy is at 140514652397128
Declaring lambda function }
Changing internal value of object at 140514652397128 from dinosaur to bird
Calling lambda function {
5 ->
In lambda function, captured value of locvar is : dinosaur
125
Calling lambda function }
Calling lambda function {
5 ->
In lambda function, captured value of locvar is : dinosaur
125
Calling lambda function }
Calling lambda function {
5 ->
In lambda function, captured value of locvar is : dinosaur
125
Calling lambda function }
Calling lambda function {
5 ->
In lambda function, captured value of locvar is : dinosaur
125
Calling lambda function }
Calling lambda function {
5 ->
In lambda function, captured value of locvar is : dinosaur
125
Calling lambda function }
Destructor of object at 140514652396968 with data "dinosaur"
Destructor of object at 140514652397040 with data "fish"
Destructor of object at 140514652397128 with data "bird"
%% Cell type:markdown id:04c99e3d tags:
How often did the copy operation for the captured variables occur inside the loop ? How did the change of the variable `locvar` after the declaration of the lambda reflect itself inside the lambda function, when it was called in the loop ? How do these results change if you use reference capture rather than value capture ?
%% Cell type:markdown id:79d13085 tags:
In the following, we examine the effects of the `mutable` keyword for lambdas. The callable objects created when the lambda expression is evaluated are by default `const`. Imagine that the compiler is automatically generating a class with an overloaded function call operator, `operator()`. The captured variables are the arguments given to the constructor. By default, the `operator()` is `const` qualified, so that the callable object may not change state, i.e., the internal copies of the captured variables it creates in its constructor can not change when the lambda is used. However, declaring the lambda as `mutable` creates a non-constant function call operator. In this case, if any of the captured values are changed inside the lambda function call, those changes survive from call to call.
Mutable lambdas are also often used in connection with init-captures. We declare a new variable in the capture bracket, using an expression to initialize it. For instance `[i = 0UL]()mutable {}` makes a variable `i` available inside the lambda, but that variable is not something that was present in the surrounding scope, but was created along with the lambda, in its constructor. It is as if the variable was being declared with an `auto i = 0UL`. We can not drop the `auto` for variable declarations anywhere in C++, except in lambda capture brackets. This is because the capture brackets define all the parameters to be given to the constructor of the lambda: so it is a context where there can only be declarations. It was therefore formulated in this way.
%% Cell type:code id:5884e24d tags:
``` C++
``` c++
{
std::string S{"Some string"};
auto L = [S=1UL]()mutable { std::cout << "Inside Lambda, S = " << S << "\n"; ++S; };
std::cout << "Outside Lambda, S= " << S << "\n";
L();
std::cout << "Outside Lambda, S= " << S << "\n";
L();
// You can understand the output by noting that despite the name S appearing in the capture
// bracket, the S was "init captured" from an unsigned long. It has no connection whatsoever
// with the variable S outside.
}
```
%% Output
Outside Lambda, S= Some string
Inside Lambda, S = 1
Outside Lambda, S= Some string
Inside Lambda, S = 2
%% Cell type:markdown id:6135039c tags:
A popular application for mutable lambdas is generator functions. The algorithm `std::generate` takes the bounds of a sequence and a callable object of no input parameters (expected to be called just as `func()` with no inputs). A similar algorithm, `std::generate_n` accepts the start of the sequence, the number of elements it contains and a callable object. The generate and assign values for the sequence elements by calling the callable object once for each of them. Here is an example. We initialize a vector to the squares of positive integers.
%% Cell type:code id:c9daa776 tags:
``` C++
``` c++
{
using namespace std;
vector<unsigned long> v, w;
generate_n(back_inserter(v), 10, [i = 0UL]() mutable {
++i;
return i * i;
});
// v = [1, 4, 9, 16 ... ]
std::cout << " v = \n";
for (auto el : v)
std::cout << el << "\n";
}
```
%% Output
v =
1
4
9
16
25
36
49
64
81
100
%% Cell type:markdown id:f882cb25 tags:
And in the following we do the same, but initialize a vector with the first 50 Fibonacci numbers...
%% Cell type:code id:7cec5bf6 tags:
``` C++
``` c++
{
using namespace std;
vector<unsigned long> v, w;
generate_n(back_inserter(w), 50, [ i = 0UL, j = 1UL ]() mutable {
i = std::exchange(j, j + i);
return i;
});
// w = [1, 1, 2, 3, 5, 8, 11 ...]
std::cout << " w = \n";
for (auto el : w)
std::cout << el << "\n";
}
```
%% Output
w =
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
9227465
14930352
24157817
39088169
63245986
102334155
165580141
267914296
433494437
701408733
1134903170
1836311903
2971215073
4807526976
7778742049
12586269025
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment