【问题标题】:How to implement flatmap using rangev3 ranges如何使用 rangev3 范围实现平面地图
【发布时间】:2025-11-26 03:10:01
【问题描述】:

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

// 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_eachyield_fromtransform 的一个微不足道的组合,但我正在努力弄清楚如何将它们连接在一起。

【问题讨论】:

    标签: c++ c++11 range-v3


    【解决方案1】:

    IIUC,您的 flatmap 函数只不过是 range-v3 的 view::for_each。试试:

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

    HTH

    【讨论】:

    • flatmap 接受两个参数,一个 A 类型的列表和一个将每个 A 映射到 B 列表的函数,它返回每个 A 转换为 B 的 aa 列表,然后展平为单个列表B. 这个使用for_each 的解决方案不是返回B 的列表列表,而不是展平列表。 (即,它的类型签名类似于[A] x (A-&gt;[B]) -&gt; [[B]] 而不是[A] x (A-&gt;[B]) -&gt; [B]。)。它可能只需要在for_eachto_vector 之间添加一些东西——也许是来自@user2807083 s anser 的action::join
    • 不,view::for_eachview::transform 相同,后跟 view::join。它在最后一步变平。
    【解决方案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;
    }
    

    【讨论】:

      【解决方案3】:

      两个答案都是正确的,但我想添加更多上下文,因为 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 ,其功能类似于 e.g. Java 的 forEach(即对范围的每个成员应用一个函数,但没有返回值)。

      如果您查看the documentation of view::for_each,您会发现它实际上是使用转换和连接实现的。

       auto   operator() (Rng &&rng, Fun fun) const -> decltype(join(transform(static_cast< Rng &&>(rng), std::move(fun))))
      

      【讨论】: