STL算法的可组合性
Composability of STL algorithms
STL算法在c++中是非常有用的。但有一件事让我有点恼火,那就是它们似乎缺乏可组合性。
例如,假设我有一个vector<pair<int, int>>
,我想把它转换成一个只包含second
元素的vector<int>
。这很简单:
std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;
std::transform(values.begin(), values.end(), std::back_inserter(result),
[] (std::pair<int, int> p) { return p.second; });
或者我想只过滤first
成员为偶数的对的vector
。也很简单:
std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> result;
std::copy_if(values.begin(), values.end(), std::back_inserter(result),
[] (std::pair<int, int> p) { return (p.first % 2) == 0; });
但是如果我两个都想做呢?没有transform_if
算法,同时使用transform
和copy_if
似乎需要分配一个临时的vector
来保存中间结果:
std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> temp;
std::vector<int> result;
std::copy_if(values.begin(), values.end(), std::back_inserter(temp),
[] (std::pair<int, int> p) { return (p.first % 2) == 0; });
std::transform(values.begin(), values.end(), std::back_inserter(result),
[] (std::pair<int, int> p) { return p.second; });
这对我来说似乎相当浪费。我能想到的避免临时向量的唯一方法是放弃transform
和copy_if
,而只使用for_each
(或常规的for循环,根据您的喜好):
std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;
std::for_each(values.begin(), values.end(),
[&result] (std::pair<int, int> p)
{ if( (p.first % 2) == 0 ) result.push_back(p.second); });
我错过了什么吗?是否有一种好方法可以将两个现有的STL算法组合成一个新的而不需要临时存储?
你说得对。您可以使用Boost。范围适配器实现组合。
我认为问题出在结构上
- c++使用两个迭代器来表示序列
- c++函数是单值的
所以你不能链接它们,因为一个函数不能返回"一个序列"
可以选择使用单对象序列(如boost中的range方法)。这样你就可以把一个处理的结果作为另一个处理的输入。(一个对象->一个对象)。
在标准c++库中,处理是(两个对象->一个对象),很明显,如果不命名临时对象,这就不能链接。
早在2000年,这个问题就已经被注意到了。Gary Powell和Martin Weiser提出了一个"视图"的概念,并创造了"视图模板库"这个名字。虽然当时没有成功,但这个想法是有道理的。"视图"适配器本质上应用动态转换。例如,它可以适应value_type
.
现在我们有了c++ 0x,这个概念可能应该重新定义。自2000年以来,我们在泛型编程方面取得了相当大的进展。
例如,让我们使用vector<pair<int, int>>
到vector<int>
的例子。这很简单:
std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, [](std::pair<int, int> p) { return p.first });
std::vector<int> result(view.begin(), view.end());
或者,使用boost::bind
技术,甚至更简单:
std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, &std::pair<int, int>::first);
std::vector<int> result(view.begin(), view.end());
自c++ 20以来,您可以将std::ranges::copy
与范围库中的std::views::filter
和std::views::values
一起使用,如下所示:
int main() {
std::vector<std::pair<int, int>> values = { {1,2}, {4,5}, {6,7}, {9,10} };
std::vector<int> result;
auto even = [](const auto& p) { return (p.first % 2) == 0; };
std::ranges::copy(values | std::views::filter(even) | std::views::values,
std::back_inserter(result));
for (int i : result)
std::cout << i << std::endl;
return 0;
}
输出:5
7
在上面的解决方案中,没有为中间结果创建临时向量,因为视图适配器创建的范围不包含元素。这些范围只是输入向量上的视图,但是具有自定义的迭代行为。
代码在Wandbox
不确定这是否仍然有效,但是…一个新的光等待头lib,可以完成您所描述的功能。文档讨论了延迟求值和com可能的生成器。
文档片段:
- 从test.txt文件中读取最多10个整数。
- 对偶数进行滤波,对其平方并求和。
int total = lz::read<int>(ifstream("test.txt")) | lz::limit(10) |
lz::filter([](int i) { return i % 2 == 0; }) |
lz::map([](int i) { return i * i; }) | lz::sum();
你可以将这一行拆分为多个表达式。
auto numbers = lz::read<int>(ifstream("test.txt")) | lz::limit(10);
auto evenFilter = numbers | lz::filter([](int i) { return i % 2 == 0; });
auto squares = evenFilter | lz::map([](int i) { return i * i; });
int total = squares | lz::sum();
- 尽管这个表达式被拆分为多个变量赋值,但它的效率并不低。
- 每个中间变量简单描述要执行的代码单元。全部放在堆栈中。
- 可组合的lambda/std::函数与std::可选
- 构建可组合有向图(扫描仪生成器的汤普森构造算法)
- 计算数组重复次数的组合的有效算法,加起来达到给定的总和
- 为 C++11 算法组合多个谓词
- 一般采用可索引/可调用的线性组合
- 返回arg_min和可选min_val的算法
- 可视化 如何将 CString 值列表添加到 MFC C++ 中的组合框中?
- 组合"%"和可选后缀时,自动属性传播有时不起作用
- 双重释放或损坏(输出):使用向量的组合算法0x0000000001a880a0***
- 可传递值影响递归算法的渐近时间复杂性
- Python到C++:使用递归列出背包的所有组合的算法
- 从给定的 IPv6:端口列表中搜索 IPv6:端口组合的最快搜索算法是什么 O(1) 时间一致性
- 所有的组合算法和解决C++问题的一般方法
- 可组合C++功能装饰器
- 如何在C++11中编写一个简单的、类型擦除的可组合迭代器
- 是硬币变化算法,输出仍可由DP解决的所有组合
- 如何对部分可重用算法进行适当的设计/架构
- STL算法的可组合性
- 未来的可组合性,以及提升::wait_for_all
- TBB线程本地设置使用可组合或enumerable_thread_specific