The Python Oracle

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

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Beneath the City Looping

--

Chapters
00:00 C++ Tools With The Same Functionality As Python'S Filter And Map
01:39 Accepted Answer Score 4
02:17 Answer 2 Score 1
03:02 Answer 3 Score 1
04:15 Thank you

--

Full question
https://stackoverflow.com/questions/2186...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #c++ #boost #functionalprogramming

#avk47



ACCEPTED ANSWER

Score 4


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



ANSWER 2

Score 1


The reason that your code is not working as you thought it would is that bind() makes a copy of all the parameters. That means that you were adding items to a copy of your std::vector<string> result; To resolve this issue you need to put your vector into a reference wrapper. That then gets copied but it contains a reference to your result vector. The change is small:

std::for_each(raw.begin(), raw.end(), std::bind(&filter_strings, std::placeholders::_1, last, std::ref(result)));

Note that I'm using the C++11 bind, not boost bind here.

Now if you want to use a lambda to keep the predicate code local to the filter you could:

std::for_each(raw.begin(), raw.end(), [&](std::string& s){ if (s > last) result.push_back(s); });

Or using std::copy_if:

std::copy_if(raw.begin(), raw.end(), std::back_inserter(result), [&](std::string& s){ return s > last; });



ANSWER 3

Score 1


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.