HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Flatmap implementation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
flatmapimplementationstackoverflow

Problem

I have an implementation of flatmap for std::vector. However, I find the repetition of the inferred return type to be ugly. And, in general, I suspect that the implementation could be cleaner or more general.

template
static auto flatmap(const std::vector &vec, FN fn) -> std::vector::type> {
    std::vector::type> result;
    for(auto x : vec) {
        auto y = fn(x);
        for( auto v : y ) {
            result.push_back(v);
        }
    }
    return result;
};


It can be used like this:

TEST_F(MarkdownUtilTests, testFlatmap) {
    std::vector v = { 1, 2, 3};
    std::vector result = flatmap(v, [](int x) { return std::vector { x, x*2, x*3 }; } );
    assertThat(result, is(std::vector {1,2,3,2,4,6,3,6,9} ));
}

Solution

There's a reason the algorithms in the standard library use iterators instead of dealing with containers directly. One is that it makes code like this quite a bit simpler. For example, using iterators, we could write Flatmap something like this:

template 
void Flatmap(InIter begin, InIter end, OutIter out, FN fn) {
    for (; begin != end; ++begin) {
        auto y = fn(*begin);
        std::copy(std::begin(y), std::end(y), out);
    }
}


This is somewhat more versatile, since it can take input from/write output to essentially any iterator, rather than necessarily reading from one vector and producing another vector. Likewise, what's produced by fn doesn't have to be a vector either--it can be essentially any container (anything for which we can use std::begin() and std::end(), to be more exact). While vector is certainly common, there are reasons for using other containers--and sometimes even a reason to write to something that isn't exactly a container either. For example:

std::vector inputs { 1, 2, 3 };

Flatmap(inputs.begin(), inputs.end(), 
        std::ostream_iterator(std::cout, "\t"), 
        [](auto x) { return std::vector { x, x * 2, x * 3 }; });


Note that I've used auto as the parameter type in the lambda, so this test code requires C++14, but Flatmap itself is fine with C++11.

If you wanted to be a bit more forward thinking, you could write this to work with ranges instead of iterators. Eric Neibler's range library is fairly nice, and seems well on its way toward being included in an upcoming C++ standard.

Along with the code being simpler and more versatile, the other obvious advantage would be that this follows the same style as standard library code--for example, its signature and usage is essentially identical to std::transform.

Code Snippets

template <typename InIter, typename OutIter, typename FN>
void Flatmap(InIter begin, InIter end, OutIter out, FN fn) {
    for (; begin != end; ++begin) {
        auto y = fn(*begin);
        std::copy(std::begin(y), std::end(y), out);
    }
}
std::vector<int> inputs { 1, 2, 3 };

Flatmap(inputs.begin(), inputs.end(), 
        std::ostream_iterator<int>(std::cout, "\t"), 
        [](auto x) { return std::vector<decltype(x)> { x, x * 2, x * 3 }; });

Context

StackExchange Code Review Q#123053, answer score: 6

Revisions (0)

No revisions yet.