9

我有一个flatmap用 C++ 实现的非常简单的函数 for std::vector,但有人建议范围通常更好。这是基于矢量的解决方案:

// flatmap: [A] -> (A->[B]) -> [B]    
template<typename T, typename FN>
static auto flatmap(const std::vector<T> &vec, FN fn) 
     -> std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> {
    std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> result;
    for(auto x : vec) {
        auto y = fn(x);
        for( auto v : y ) {
            result.push_back(v);
        }
    }
    return result;
};

也有人建议我使用迭代器,但这破坏了函数的良好可组合性:

map(filter(flatmap( V, fn), fn2), fn3)

我假设在 range-v3 世界中,我的目标是将上述内容编写为:

auto result = v | flatmap(fn) | filter(fn2) | transform(fn3);

感觉flatmap应该只是 , 和 的一个微不足道的组合,views::for_each但我正在努力弄清楚如何将它们全部连接在一起。yield_fromtransform

4

3 回答 3

10

IIUC,您的flatmap功能不过是 range-v3's view::for_each。尝试:

using namespace ranges; auto result = v | view::for_each(fn) | to_vector;

HTH

于 2016-03-17T17:34:01.470 回答
2

如果我理解正确,你的函数flatmap必须做什么,你可以把它写成v | view::transform(fn) | action::join. 这是使用范围制作的示例:

#include <range/v3/all.hpp>

#include <iostream>
#include <string>
#include <utility>
#include <vector>


// flatmap: [A] -> (A->[B]) -> [B]
template<typename T, typename FN>
static auto flatmap(const std::vector<T> &vec, FN fn)
     -> std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> {
    std::vector<typename std::remove_reference<decltype(fn(T())[0])>::type> result;
    for(auto x : vec) {
        auto y = fn(x);
        for( auto v : y ) {
            result.push_back(v);
        }
    }
    return result;
};

// This will be test function for both flatmap and range usage
std::vector<std::string> testFn(int n)
{
    std::vector<std::string> result;
    int ofs = 0;
    for(int i = 0; i < n; ++i)
    {
        char initialChar = 'A' + ofs;
        std::string partialResult = "\"";
        for(int j = 0; j <=i; ++j, ++ofs)
        {
            partialResult.append(1, initialChar+j);
        }
        partialResult += "\"";
        result.push_back(partialResult);
    }
    return std::move(result);
}

int main(int, char**)
{
    std::vector<int> vv {1, 2, 3, 4, 5, 6};
    // test flatmap
    auto r2 = flatmap(vv, testFn);
    for(auto s:r2)
    {
        std::cout << s << " " ;
    }
    std::cout << "\n";

    using namespace ranges;

    // test ranges equivalent
    auto rng = vv|view::transform(testFn)|action::join;
    //         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is an equivalent for flatmap in ranges terms

    for(auto s:rng)
    {
        std::cout << s << " ";
    }
    std::cout << "\n";

    std::cout << std::flush;
    return 0;
}
于 2016-03-17T10:52:50.947 回答
2

两个答案都是正确的,但我想添加更多上下文,因为 for_each 的命名可能有点令人困惑(它确实让我感到困惑)。这是一个示例,您可以使用它来验证它view::for_each实际上是 range 的 flatMap:

#include <range/v3/all.hpp>
#include <iostream>
#include <vector>
using namespace ranges;

int main()
{
    const std::vector<int> a = { 0, 1, 2 };
    auto b = a | view::for_each([] (int x) { return view::ints(x, x+3); });

   ranges::for_each( b, [] (int x) { std::cout << x << " "; } );
   std::cout << std::endl;
}

这将打印0 1 2 1 2 3 2 3 4. 该示例还显示了可能造成混淆的原因,因为在 range 命名空间中实际上有一个函数 for_each,其功能类似于 Java 的 forEach (即将一个函数应用于一个范围的每个成员而没有返回值)。

如果您查看文档,view::for_each您会发现它是使用转换和连接来实现的。

 auto   operator() (Rng &&rng, Fun fun) const -> decltype(join(transform(static_cast< Rng &&>(rng), std::move(fun))))
于 2018-09-23T12:58:31.323 回答