How it works...

In the preceding examples, we have implemented the map in a functional way, without side-effects. That means it preserves the original range and returns a new one. The arguments of the function are the function to apply and the range. In order to avoid confusion with the std::map container, we have called this function mapf. There are several overloads for mapf as shown earlier:

  • The first overload is for containers that support iterating and assignment to its elements; this includes std::vector, std::list, and std::array, but also C-like arrays. The function takes an rvalue reference to a function and a range for which std::begin() and std::end() are defined. The range is passed by value so that modifying the local copy does not affect the original range. The range is transformed by applying the given function to each element using the standard algorithm std::transform(); the transformed range is then returned.
  • The second overload is specialized for std::map that does not support direct assignment to its elements (std::pair<T, U>). Therefore, this overload creates a new map, then iterates through its elements using a range-based for loop, and inserts into the new map the result of applying the input function to each element of the original map.
  • The third overload is specialized for std::queue, which is a container that does not support iterating. It can be argued that a queue is not a typical structure to map over, but for the sake of demonstrating different possible implementations, we are considering it. In order to iterate over the elements of a queue, the queue must be altered--you need to pop elements from the front until the list is empty. This is what the third overload does--it processes each element of the input queue (passed by value) and pushes the result of applying the given function to the front element of the remaining queue.

Now that we have these overloads implemented, we can apply them to a lot of containers, as shown in the following examples (notice that the map and fold functions used here are implemented in a namespace called funclib in the code accompanying the book and therefore shown with the fully qualified name):

  • Retain absolute values from a vector. In this example, the vector contains both negative and positive values. After applying the mapping, the result is a new vector with only positive values.
        auto vnums =  
std::vector<int>{0, 2, -3, 5, -1, 6, 8, -4, 9};
auto r = funclib::mapf([](int const i) {
return std::abs(i); }, vnums);
// r = {0, 2, 3, 5, 1, 6, 8, 4, 9}
  • Square the numerical values of a list. In this example, the list contains integral values. After applying the mapping, the result is a list containing the squares of the initial values.
        auto lnums = std::list<int>{1, 2, 3, 4, 5}; 
auto l = funclib::mapf([](int const i) {
return i*i; }, lnums);
// l = {1, 4, 9, 16, 25}
  • Rounded amounts of floating point. For this example, we need to use std::round(); however, this has overloads for all floating point types, which makes it impossible for the compiler to pick the right one. As a result, we either have to write a lambda that takes an argument of a specific floating point type and returns the value of std::round() applied to that value or create a function object template that wraps std::round() and enables its call operator only for floating point types. This technique is used in the following example:
        template<class T = double> 
struct fround
{
typename std::enable_if<
std::is_floating_point<T>::value, T>::type
operator()(const T& value) const
{
return std::round(value);
}
};

auto amounts =
std::array<double, 5> {10.42, 2.50, 100.0, 23.75, 12.99};
auto a = funclib::mapf(fround<>(), amounts);
// a = {10.0, 3.0, 100.0, 24.0, 13.0}

  • Uppercase the string keys of a map of words (where the key is the word and the value is the number of appearances in the text). Note that creating an uppercase copy of a string is itself a mapping operation. Therefore, in this example, we use mapf to apply toupper() to the elements of the string representing the key in order to produce an uppercase copy:
        auto words = std::map<std::string, int>{  
{"one", 1}, {"two", 2}, {"three", 3}
};
auto m = funclib::mapf(
[](std::pair<std::string, int> const kvp) {
return std::make_pair(
funclib::mapf(toupper, kvp.first),
kvp.second);
},
words);
// m = {{"ONE", 1}, {"TWO", 2}, {"THREE", 3}}
  • Normalize values from a queue of priorities--initially, the values are from 1 to 100, but we want to normalize them into two values, 1=high and 2=normal. All initial priorities that have a value up to 30 become a high priority, the others get a normal priority:
        auto priorities = std::queue<int>(); 
priorities.push(10);
priorities.push(20);
priorities.push(30);
priorities.push(40);
priorities.push(50);
auto p = funclib::mapf(
[](int const i) { return i > 30 ? 2 : 1; },
priorities);
// p = {1, 1, 1, 2, 2}

To implement fold, we actually have to consider the two possible types of folding, that is, from left to right and from right to left. Therefore, we have provided two functions called foldl (for left folding) and foldr (for right folding). The implementations shown in the previous section are very similar--they both take a function, a range, and an initial value and call std::algorithm() to fold the values of the range into a single value. However, foldl uses direct iterators, whereas foldr uses reverse iterators to traverse and process the range. The second overload is a specialization for type std::queue, which does not have iterators.

Based on these implementations for folding, we can do the following examples:

  • Adding the values of a vector of integers. In this case, both left and right folding will produce the same result. In the following examples, we pass either a lambda that takes a sum and a number and returns a new sum or the function object std::plus<> from the standard library that applies operator+ to two operands of the same type (basically similar to the closure of the lambda):
        auto vnums =  
std::vector<int>{0, 2, -3, 5, -1, 6, 8, -4, 9};

auto s1 = funclib::foldl(
[](const int s, const int n) {return s + n; },
vnums, 0); // s1 = 22

auto s2 = funclib::foldl(
std::plus<>(), vnums, 0); // s2 = 22

auto s3 = funclib::foldr(
[](const int s, const int n) {return s + n; },
vnums, 0); // s3 = 22

auto s4 = funclib::foldr(
std::plus<>(), vnums, 0); // s4 = 22
  • Concatenating strings from a vector into a single string:
        auto texts =  
std::vector<std::string>{"hello"s, " "s, "world"s, "!"s};

auto txt1 = funclib::foldl(
[](std::string const & s, std::string const & n) {
return s + n;},
texts, ""s); // txt1 = "hello world!"

auto txt2 = funclib::foldr(
[](std::string const & s, std::string const & n) {
return s + n; },
texts, ""s); // txt2 = "!world hello"

  • Concatenating an array of characters into a string:
        char chars[] = {'c','i','v','i','c'}; 

auto str1 = funclib::foldl(std::plus<>(), chars, ""s);
// str1 = "civic"

auto str2 = funclib::foldr(std::plus<>(), chars, ""s);
// str2 = "civic"
  • Counting the number of words from a text based on their already computed appearances available in a map<string, int>:
        auto words = std::map<std::string, int>{  
{"one", 1}, {"two", 2}, {"three", 3} };

auto count = funclib::foldl(
[](int const s, std::pair<std::string, int> const kvp) {
return s + kvp.second; },
words, 0); // count = 6
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset