我试图使用花哨的-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
这种流方法允许更多的容器类型选择自由(因为几个回答者似乎赞成)和懒惰的评估。
答案 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_type
由Key,Value
对组成的其他容器(如std::map
,std::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);
}
答案 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。
编辑:如果您尝试使用非标准分配器传递矢量,这也将表现得很愚蠢(读取:未编译)。