使用auto的Functional C ++ map combinator

时间:2015-10-11 19:23:54

标签: c++ functional-programming auto c++-standard-library combinators

我试图使用花哨的-std = c ++ 14功能来实现" map"你在函数式语言中看到的组合子(不要与std :: map混淆)。我的最终目标是写一个"外观模式"函数式编程的标题允许我在大多数时间忘记副作用和迭代器。我在https://gist.github.com/phatak-dev/766eccf8c72484ad623b找到了一个志同道合的人的帖子。 Madhukara的地图版本看起来像

template <typename Collection,typename unop>
  Collection map(Collection col,unop op) {
  std::transform(col.begin(),col.end(),col.begin(),op);
  return col;
}

只要您不要求愚蠢的东西,它似乎完美无缺,但返回类型必须与输入集合相同。我尝试推广具有不同类型的域和范围的尝试如下:

template <typename Collection, typename function> 
auto map(function f, Collection c)  {
  auto result;
  std::transform(c.begin(),c.end(),result.begin(),f);
  return result;
}

这不会编译,但希望某些人能够清楚地知道我想要做什么...我想初始化一个空的同类型容器-c输出类型为f,然后将f(c[i])放入其中。编译器抱怨声明“自动结果”#39;没有初始化者,但我不知道如何要求空洞的任何人。有没有办法调整那条线,让它做我想做的事情?我之前从未尝试过任何与异国情调有关的事情,所以欢迎任何其他建议。

谢谢!

约翰

编辑:这是一个充满希望的例子:

auto first_letter = [](string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map(first_letter, words); // {'h','w'}

编辑2:这是另一种使用重量级&#34;流&#34;库(不要与IO流混淆)实现&#34;迭代器模式&#34;,就像Java的Streams:

http://jscheiny.github.io/Streams/

Java方法: http://tutorials.jenkov.com/java-collections/streams.html

这种流方法允许更多的容器类型选择自由(因为几个回答者似乎赞成)和懒惰的评估。

4 个答案:

答案 0 :(得分:3)

只需使用boost::adaptors::tranformed

#include <boost/range/adaptor/transformed.hpp>

template <typename Collection, typename function> 
auto map(function f, Collection c)  {
  return c | boost::adaptors::transformed(f);
}

使用此范围 - 您可以创建任何所需的容器。

char first_letter(string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
auto transformed_range = map(first_letter, words);
vector<char> first_letters(begin(transformed_range ), end(transformed_range ));

如果你坚持让map函数返回容器,而不是范围 - 再向该函数模板添加一个参数:

#include <boost/range/adaptor/transformed.hpp>

template <typename Result, typename Collection, typename function> 
auto map(function f, Collection c)  {
  auto transformed_range = c | boost::adaptors::transformed(f);
  return Result(begin(transformed_range), end(transformed_range));
}

char first_letter(string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map<vector<char>>(first_letter, words); 

但是如果你真的坚持要有你想要的确切行为 - 你必须知道如何将集合类型转换为具有转换值的其他集合类型。

首先 - 获得new_value_type的方法:

template <typename Function, typename OldValueType>
struct MapToTransformedValue
{
    using type = decltype(std::declval<Function>()(std::declval<OldValueType>()));
};

一般特征:

template <typename Function, typename Container>
struct MapToTransformedContainer;

最简单的案例 - 适用于std::array

// for std::array
template <typename Function, typename OldValueType, std::size_t N> 
struct MapToTransformedContainer<Function, std::array<OldValueType, N>>
{
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
    using type =  std::array<value_type, N>;
};

对于std::vector - 稍微复杂一点 - 你需要为std分配器提供新的分配器 - 你可以使用它的重新绑定模板:

// for std::vector
template <typename Function, typename OldValueType, typename OldAllocator> 
struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>>
{
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
    using allocator = typename OldAllocator::template rebind<value_type>::other;
    using type =  std::vector<value_type, allocator>;
};

所以你的功能如下所示:

template <typename Collection, typename function> 
auto map(function f, Collection c)  
{
    using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type;
    auto transformed_range = c | boost::adaptors::transformed(f);
    return NewCollectionType (begin(transformed_range), end(transformed_range));
}

现在 - 您的main()符合要求:

char first_letter(std::string const& s)  { return s[0]; }

int main() {
    std::vector<std::string> words; 
    words.push_back("hello"); words.push_back("world");
    auto first_letters = map(first_letter, words); 
    std::cout << first_letters[0] << std::endl;
}

请注意,对于其中value_typeKey,Value对组成的其他容器(如std::mapstd::set(及其无序的_...兄弟姐妹),您必须定义另一个MapToTransformedContainer ...

答案 1 :(得分:1)

我会利用大多数容器都有一个带有一对迭代器的构造函数的事实。

#include <boost/iterator/transform_iterator.hpp>

template <typename Function, typename Collection>
struct map_to
{
    Function f;
    const Collection& c;

    template <typename T>
    operator T() &&
    {
        using std::begin; using std::end;
        return { boost::make_transform_iterator(begin(c), f)
               , boost::make_transform_iterator(end(c), f) };
    }
};

template <typename Function, typename Collection> 
map_to<Function, Collection> map(Function f, const Collection& c)
{
    return { f, c };
}

试验:

int main()
{
    std::vector<std::string> words; 

    words.push_back("hello");
    words.push_back("world");

    auto first_letter = [](std::string s) { return s[0]; };

    std::vector<char> v = map(first_letter, words);
    std::set<char> s = map(first_letter, words);
    std::forward_list<char> f = map(first_letter, words);
}

DEMO

答案 2 :(得分:0)

只是为了它的乐趣,我在这里解决了这个问题:

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>

template <template<typename...> class Collection> 
struct mapper {
    template<typename function, typename T, typename... Rest>
    static auto map(function f, const Collection<T, Rest...>& c)  {
      Collection<decltype(f(*c.begin()))> result;
      std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f);
      return result;
    }
};

int main()
{
    // Example 1
    std::vector<int> v{0, 1};
    auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v);
    for (const auto& f : fv) { std::cout << f << " "; }

    std::cout << "\n";

    // Example 2 
    std::set<float> m{1, 2, 3, 4}; 
    auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val / 2.0f); }, m);
    for (const auto& f : fm) { std::cout << f << " "; }
}

请注意,只有当您对除输出容器的type参数之外的所有内容的默认值感到满意时,这才会起作用。

答案 3 :(得分:0)

我有相同的任务,如果可以减少样板,可以在功能上妥协。

这就是我的最终结论(可能无法使用使用多个显式arg或无法轻易推断出其模板args的容器,但您会同意使用它是干净的)。

using std::vector;

template<typename Src, typename Dst, template<class, typename ...> typename Container>
Container<Dst> fmap(Container<Src>& container, Dst(*f)(Src)) {
    Container<Dst> result;
    result.reserve(container.size());
    std::transform(container.begin(), container.end(), std::back_inserter(result), f);
    return result;
}

int main() {
    vector<int> a = {1, 2, 3};
    auto f = [](int x) -> double { return (x + 1)/2; };
    vector<double> b = fmap(a, +f);
    for (auto x: b) {
        std::cout << x << std::endl;
    }
    return 0;
}

对我来说,一个主要要求是不要使它变得丑陋,并且不应该使用boost。

编辑:如果您尝试使用非标准分配器传递矢量,这也将表现得很愚蠢(读取:未编译)。