If you wish to learn functional programming in C++, there is no better place to start than the range-v3 library, the predecessor of C++ Ranges. This page provides functional programming solutions to commonly encountered programming problems with range-v3. You can find many more practical examples in the recently published book.
Getting started
If you experience problems loading range-v3 in Wandbox, use Godbolt:
- Go to: https://godbolt.org/
- Choose x86-64 GCC trunk
- In the Library menu choose range-v3
- In the Output menu choose Execute the code
- Insert the following code and make sure it compiles:
Resistors in parallel
- Invert the values of resistance using views::transform. This will convert
[20,10,15]
to[0.05,0.1,0.0666667]
.- Use accumulate to sum up the transformed resistance values.
- Invert the result of the accumulation to arrive at
4.61538
Ω. #include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto v = {20,10,15}; auto r_inv = v | views::transform([](int x){return 1.0 / x;}); // [0.05,0.1,0.0666667] auto val = 1.0 / fold_left(r_inv,0.0,std::plus{}); // 1.0 / 0.216667 = 4.61538 //auto val = 1.0 / accumulate(r_inv,0.0); // 1.0 / 0.216667 = 4.61538 std::cout << val; }
Binary to decimal conversion
- Reverse the initial binary representation using views::reverse arriving at
[0,1,1,1]
. This will allow us to start from the power 20.- Generate the range
[0,1,2,3]
using views::iota.- Use views::transform and the shift operator to convert
[0,1,2,3]
to[20, 21, 22, 23]
.- Use views::inner_product to combine the reversed binary range
[0,1,1,1]
with the powers of two[1,2,4,8]
and sum them up. #include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto const v = std::vector<uint8_t> {1,1,1,0}; auto r_rev = v | views::reverse; // [0,1,1,1] auto r_int = views::iota(0, distance(v)); // [0,1,2,3] auto r_pow = r_int | views::transform([](int x){ return 1 << x;}); // [1,2,4,8] auto val = inner_product(r_rev,r_pow,0); // 0*1+1*2+1*4+1*8 = 14 std::cout << val; }
snake_case to CamelCase
- Split the string into separate words at underscore using views::split arriving at
[[f,e,e,l],[t,h,e],[f,o,r,c,e]]
.- Take the first letter of every word using views::take(1) and capitalize it using views::transform(...std::toupper...) , e.g. for
[f,e,e,l]
we get[F]
.- Concatenate the first capitalized letter of a word with its tail using views::concat, e.g. for
[F]
and[e,e,l]
we get[F,e,e,l]
.- Flatten the range of words
[[F,e,e,l],[T,h,e],[F,o,r,c,e]]
into a single range[F,e,e,l,T,h,e,F,o,r,c,e]
using views::join.#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto const s = std::string{"feel_the_force"}; auto words = s | views::split('_'); // [[f,e,e,l],[t,h,e],[f,o,r,c,e]] auto words_cap = words | views::transform([](auto w){ auto head = w | views::take(1) | views::transform([](unsigned char c){return std::toupper(c);}); // e.g. [F] return views::concat(head, w | views::tail); // e.g. [F,e,e,l] }); // [[F,e,e,l],[T,h,e],[F,o,r,c,e]] auto s_camelcase = words_cap | views::join | to<std::string>(); // FeelTheForce std::cout << s_camelcase; }
Would you like to get more practice?
Fibonacci sequence
Use views::generate with a mutable lambda. The keyword mutable allows us to update the initial pair p
[0,1]
in the body of the expression in the following way:Note: To generate an element of the sequence we return only a single element. Returning a pair would result in duplicates in the output.
- The first element of the new pair is the second element of the old pair, i.e.
b0
.- The second element of the new pair is the sum of the elements of the old pair, i.e.
a0 + b0
.#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto rng = views::generate( [p = std::pair{0,1}] () mutable { auto [a0,b0] = p; p = {b0, a0 + b0}; return a0; }); auto fib10 = rng | views::take(10); // [0,1,1,2,3,5,8,13,21,34] std::cout << fib10; }
Caesar cipher
Note: Single words only.
- Generate the cyclic alphabet using views::closed_iota followed by views::cycle getting
[a,b,c...a,b,c...]
.- Using views::drop drop the number of characters that corresponds to the shift getting
[l,m,n...a,b,c...]
- Process every letter of the word "apple" using views::for_each. For example, for the letter
e
drop ('e'-'a') = (101 - 97) = 4 characters from the shifted alphabet. This means that[l,m,n...]
becomes[p,q,r...]
. To get the new lettere
take the first letter of the transformed sequence, i.e.p
.#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto shift = 11; auto const s = std::string{"apple"}; auto alphabet = views::closed_iota('a','z') | views::cycle; // [a,b,c...a,b,c...] auto shifted_alphabet = alphabet | views::drop(shift); // [l,m,n...a,b,c...] auto encrypted = s | views::for_each([shifted_alphabet](char letter){ return shifted_alphabet | views::drop(letter - 'a') | views::take(1); }); // [l,a,a,w,p] auto s_encrypted = encrypted | to<std::string>(); // laawp std::cout << s_encrypted; }
Triangular sequence
- Generate the range
[1,2,3...]
using views::iota.- Modify the generated range using views::transform(...n*(n+1)/2...) getting
[1,3,6..]
.- Take the desired number of elements using views::take.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto r_int = views::iota(1); // [1,2,3,4,5,6,7,8...] auto r_triseq = r_int | views::transform([](int n){return n*(n+1)/2;}); // [1,3,6,10,15,21,28...] auto r_first5 = r_triseq | views::take(5); // [1,3,6,10,15] std::cout << r_first5; }Note: Range-v3 has a convenience view to accomplish the same task, namely views::partial_sum.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto r_int = views::iota(1); // [1,2,3,4,5,6,7,8...] auto r_triseq = r_int | views::partial_sum(std::plus{}); // [1,3,6,10,15,21,28...] std::cout << (r_triseq | views::take(5)); // [1,3,6,10,15] }
Aronson's sequence
Aronson's sequence is a self-describing integer sequence defined by the sentence "T is the first, fourth, eleventh, sixteenth, twenty-fourth, twenty-ninth, thirty-third... letter in this sentence." Generate the sequence ignoring spaces and punctuation.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto s = std::string{"T is the first, fourth, eleventh, sixteenth, " "twenty-fourth, twenty-ninth, thirty-third..."}; auto r_letters_only = s | views::filter([](unsigned char c){ return std::isalpha(c);}); // [T,i,s,t,h,e,f,i,r,s,t,f,o,u,r,t,h...] auto r_lowercase = r_letters_only | views::transform([](unsigned char c){ return std::tolower(c);}); // [t,i,s,t,h,e,f,i,r,s,t,f,o,u,r,t,h...] auto r_pairs = views::zip(r_lowercase, views::iota(1)); // (t:1)(i:2)(s:3)(t:4)... auto r_only_tpairs = r_pairs | views::filter([](auto && r){return r.first == 't';}); // (t:1)(t:4)... auto r_aronson = r_only_tpairs | views::values; // [1,4,11,16,24,29,33,35,39,45,47,51,56,58,62,64] std::cout << r_aronson; }
Shannon's entropy
Shannon's entropy is a measure of system's disorganisation. If all the balls in the bag below are red, the probability vector would be [1,0,0,0], hence the resulting entropy would be 0. We can interpret this as that there would be no surpise in the outcome. The example below demonstrates the entropy calculation for 4 red, 2 gray, one black and one white ball. To calculate the entropy:
- Transform the probability vector using views::transform(...-p*std::log2(p)...).
- Sum up the elements of the transformed probability range using fold_left or accumulate.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto const v = std::vector{0.5,0.25,0.125,0.125}; auto r_p_logp = v | views::transform([](auto p){return -p*std::log2(p);}); auto val = fold_left(r_p_logp, 0.0, std::plus{}); // 1.75 //auto val = accumulate(r_p_logp, 0.0); // 1.75 std::cout << val; }
Dot product
Note: This is a rough equivalent of ranges::inner_product.
To get the dot product:
- Multiply the corresponding elements of the input vectors with each other using views::zip_with(std::multiplies{}...), e.g. here
[1.5*1, 2.5*2, 3.5*3]
.- Sum up the values of the resulting range using accumulate, e.g. here
1.5 + 5 + 10.5
.#include <iostream> #include <range/v3/all.hpp> using namespace ranges; auto dotProduct = [](auto && r1, auto && r2){ return accumulate( views::zip_with(std::multiplies{},r1,r2), 0.0);}; int main(){ auto const v1 = std::vector{1.5,2.5,3.5}; auto const v2 = std::vector{1,2,3}; std::cout << dotProduct(v1,v2); // 1.5*1 + 2.5*2 + 3.5*3 = 17 }
Permutations with repetition
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto const v = std::vector{'a','b','c'}; auto rng = views::cartesian_product(v,v,v); for(auto const & [x,y,z] : rng) std::cout << x << ' ' << y << ' ' << z << '\n'; }// 27 permutations with repetition // a a a // a a b // a a c // a b a // a b b // ...
Permutations without repetition
Note: Sort the sequence first.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto s = std::string{"cab"}; auto s2 = s; sort(s2); do { std::cout << s2 << '\n'; } while (next_permutation(s2)); }// abc // acb // bac // bca // cab // cba
Estimate area under a function
Estimate the area under the function x3 on the interval (0,2) using the midpoint Riemann sums. The figure below depicts approximately half of the area of interest.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; int main(){ auto steps = 5; auto a = 0.0; auto b = 2.0; auto dx = (b-a)/steps; // 0.4 auto f_cubic = [](double x){return x*x*x;}; auto r_int = views::iota(0, steps); // [0,1,2,3,4] auto r_pos = r_int | views::transform([dx](int i){return dx*(0.5 + i);}); // [0.2,0.6,1,1.4,1.8] auto r_cubic = r_pos | views::transform(f_cubic); // [0.008, 0.216, 1, 2.744, 5.832] auto area = dx*accumulate(r_cubic, 0.0); // 0.4*(0.008 + 0.216 + 1 + 2.744 + 5.832) = 3.92 std::cout << area; }
Tagged: Range-v3