Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ tools with the same functionality as Python's filter and map

I am looking for a C++ analogs for a map or filter from Python programming language. The first of them applies some function to every item of iterable and return a list of the results, the second constructs a list from those elements of iterable for which function returns true.

I would like to use the similar functionality in C++:

  • Map some function to the container in order to get new container with a transformed data (and probably with a different length);
  • Use some kind of conditional filtering for a container;

Are there any fine implementations of Python's map and filter in C++?

In this short example I am trying to work it out using such tools as boost::bind and std::for_each and I face with a difficulties. The std::vector<std::string> result should contain all the strings std::vector<std::string> raw that lexicographicaly higher than the last string from stdin. But in fact the result container is still empty at the return point.

#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/bind.hpp>

void filter_strings(std::string& current, std::string& last, std::vector<std::string>& results)
{
    if (current > last)
    {
        results.push_back(current);
        std::cout << "Matched: " << current << std::endl;
    }
}

int main()
{
    std::vector<std::string> raw, result;
    std::string input, last;

    //Populate first container with a data
    while(std::getline(std::cin, input))
        raw.push_back(input);
    last = raw.back();

    //Put into result vector all strings which lexicographically higher than the last one
    std::for_each(raw.begin(), raw.end(), boost::bind(&filter_strings, _1, last, result));

    //For some reason the resulting container is empty
    std::cout << "Results: " << result.size() << std::endl;

    return 0;
}

The input and the output:

[vitaly@thermaltake 1]$ ./9_boost_bind 
121
123
122
120                      //Ctrl+D key press
Matched: 121
Matched: 123
Matched: 122
Results: 0

Any help will be appreciated.

like image 278
Vitaly Isaev Avatar asked Feb 18 '14 21:02

Vitaly Isaev


2 Answers

As @juanchopanza has suggested, the template functions in the <algorithm> STL header are your best bet.

#include <iostream>
#include <vector>


std::vector<std::string> filter(std::vector<std::string> & raw) {
    std::vector<std::string> result(raw.size());
    std::string last = raw[raw.size() - 1];
    auto it = std::copy_if(raw.begin(), raw.end(), result.begin(),
        [&](std::string s) { return s.compare(last) > 0; });
    result.resize(std::distance(result.begin(), it));
    return result;
}

int main(int argc, const char *argv[])
{
    std::vector<std::string> raw, result;
    std::string input;
    while (std::getline(std::cin, input)) {
        raw.push_back(input);
    }

    result = filter(raw);

    for (size_t i = 0; i < result.size(); i++) {
        std::cout << "Matched: " << result[i] << std::endl;
    }
    std::cout << "Results: " << result.size() << std::endl;
    return 0;
}

Compile and run:

$ clang++ -std=c++11 -o cppfilter main.cpp && ./cppfilter
121
123
122
120  // Ctrl + D pressed
Matched: 121
Matched: 123
Matched: 122
Results: 3
like image 158
logc Avatar answered Oct 21 '22 02:10

logc


To make your current code work, you have to wrap the result argument to boost::bind inside boost::ref(), otherwise bind will make a copy of your result.

Otherwise, the commenters @juanchopanza and @alexbuisson already gave good answers on this.

Using the plain C++11 standard library (i.e. without Boost), you could implement your above program by replacing the std::for_each() with the following (note that the filter_strings function is not needed anymore and you neeed to #include <iterator> for std::back_inserter):

std::copy_if(raw.begin(), raw.end(), std::back_inserter(result),
    [&](std::string const& current) -> bool {
        if (current > last)
        {
            std::cout << "Matched: " << current << std::endl;
            return true;
        }
        return false;
    }
);

Although this is (probably, if you know the STL) better than your initial approach with custom push_back in for_each it still does not look very nice. Generally, more readable code can be written using Boost.Range, where you can find nearly 1:1 replacements for map and filter: filtered and transformed. For the program above, these would not be particularly helpful, but especially for chained map/filter, using Boost.Range tends to help.

like image 40
Oberon Avatar answered Oct 21 '22 01:10

Oberon