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:
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:
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.
Note: To generate an element of the sequence we return only a single element. Returning a pair would result in duplicates in the output.
#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 letter e 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;
}