什么是同时迭代两个或多个容器的最佳方法
C ++ 11提供了多种方式来遍历容器。 例如:
基于范围的循环
for(auto c : container) fun(c)
的std :: for_each的
for_each(container.begin(),container.end(),fun)
然而,推荐的方式是遍历两个(或更多)相同大小的容器来完成类似的操作:
for(unsigned i = 0; i < containerA.size(); ++i) { containerA[i] = containerB[i]; }
对于你的具体例子,只是使用
std::copy_n(contB.begin(), contA.size(), contA.begin())
对于更一般的情况,可以使用zip_iterator
的zip_iterator
,使用一个小函数使其在基于范围的for循环中可用。 对于大多数情况下,这将工作:
template<class... Conts> auto zip_range(Conts&... conts) -> decltype(boost::make_iterator_range( boost::make_zip_iterator(boost::make_tuple(conts.begin()...)), boost::make_zip_iterator(boost::make_tuple(conts.end()...)))) { return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)), boost::make_zip_iterator(boost::make_tuple(conts.end()...))}; } // ... for(auto&& t : zip_range(contA, contB)) std::cout << t.get<0>() << " : " << t.get<1>() << "\n";
现场示例。
然而,对于全面的通用性,你可能想要更类似于这样的东西,这对于没有成员begin()
/ end()
但是在其名称空间中具有begin
/ end
函数的数组和用户定义types。 而且,这将允许用户通过zip_c...
函数专门获得const
访问权限。
如果你是像我这样的好错误消息的提倡者,那么你可能会想要这个 ,它检查是否有任何临时容器被传递给任何zip_...
函数,如果是的话,打印一个很好的错误信息。
晚会晚了。 但是:我会遍历索引。 但不是与经典for
循环,而是与指数的基于范围的循环:
for(unsigned i : indices(containerA)) containerA[i] = containerB[i];
indices
是一个简单的包装函数,它返回一个(懒惰评估)的指数范围。 由于实现 – 尽pipe很简单 – 有点太长, 不能在这里发布, 你可以在GitHub上find一个实现 。
这个代码和使用手动循环一样高效 。
如果这种模式经常出现在您的数据中,请考虑使用另一种模式,即将两个序列进行zip
并生成一系列与所配对的元素相对应的元组:
for (auto items&& : zip(containerA, containerB)) get<0>(items) = get<1>(items);
zip
的实现是读者的一个练习,但是它很容易从实现indices
。
我想知道为什么没有人提到这个:
auto ItA = VectorA.begin(); auto ItB = VectorB.begin(); while(ItA != VectorA.end() || ItB != VectorB.end()) { if(ItA != VectorA.end()) { ++ItA; } if(ItB != VectorB.end()) { ++ItB; } }
PS:如果容器大小不匹配,那么你将不得不把代码放在if语句中。
在algorithm
头文件中提供了多个容器的特定事物 。 例如,在你给出的例子中,你可以使用std::copy
而不是显式的for循环。
另一方面,没有任何内置的方式来一般迭代多个容器,而不是正常的循环。 这并不奇怪,因为有很多方法来迭代。 考虑一下:你可以通过一个步骤迭代一个容器,一个容器与另一个步骤; 或通过一个容器,直到它结束,然后开始插入,而你通过到另一个容器的末尾; 或者每次完全通过另一个容器时,第一个容器的一个步骤,然后重新开始; 或其他一些模式; 或一次超过两个容器; 等等
但是,如果你想制作自己的 “for_each”风格的函数,它只能通过两个容器迭代到最短的时间,你可以这样做:
template <typename Container1, typename Container2> void custom_for_each( Container1 &c1, Container2 &c2, std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f) { Container1::iterator begin1 = c1.begin(); Container2::iterator begin2 = c2.begin(); Container1::iterator end1 = c1.end(); Container2::iterator end2 = c2.end(); Container1::iterator i1; Container1::iterator i2; for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) { f(i1, i2); } }
很明显,你可以用类似的方式制作任何你想要的迭代策略。
当然,你可能会争辩说,直接做内循环比编写像这样的自定义函数要容易得多,如果你只打算做一两次,那么你就是对的。 但好的是,这是非常可重用的。 =)
如果你只需要同时迭代2个容器,在boost范围库中有一个扩展的for_eachalgorithm,例如:
#include <vector> #include <boost/assign/list_of.hpp> #include <boost/bind.hpp> #include <boost/range/algorithm_ext/for_each.hpp> void foo(int a, int& b) { b = a + 1; } int main() { std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2); std::vector<int> contB(contA.size(), 0); boost::for_each(contA, contB, boost::bind(&foo, _1, _2)); // contB will be now 5,4,6,3 //... return 0; }
当你需要在一个algorithm中处理两个以上的容器时,你需要使用zip。
另一种解决scheme可能是捕获lambda中另一个容器的迭代器的引用,并使用后增加运算符。 例如简单的复制将是:
vector<double> a{1, 2, 3}; vector<double> b(3); auto ita = a.begin(); for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })
在lambda里面,你可以用ita
做任何事情,然后增加它。 这很容易延伸到多个容器的情况。
范围库提供了这个和其他非常有用的function。 以下示例使用Boost.Range 。 Eric Niebler的range3应该是一个很好的select。
#include <boost/range/combine.hpp> #include <iostream> #include <vector> #include <list> int main(int, const char*[]) { std::vector<int> const v{0,1,2,3,4}; std::list<char> const l{'a', 'b', 'c', 'd', 'e'}; for(auto const& i: boost::combine(v, l)) { int ti; char tc; boost::tie(ti,tc) = i; std::cout << '(' << ti << ',' << tc << ')' << '\n'; } return 0; }
C ++ 17将使用结构化绑定更好:
int main(int, const char*[]) { std::vector<int> const v{0,1,2,3,4}; std::list<char> const l{'a', 'b', 'c', 'd', 'e'}; for(auto const& [ti, tc]: boost::combine(v, l)) { std::cout << '(' << ti << ',' << tc << ')' << '\n'; } return 0; }
这是一个变体
template<class ... Iterator> void increment_dummy(Iterator ... i) {} template<class Function,class ... Iterator> void for_each_combined(size_t N,Function&& fun,Iterator... iter) { while(N!=0) { fun(*iter...); increment_dummy(++iter...); --N; } }
用法示例
void arrays_mix(size_t N,const float* x,const float* y,float* z) { for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z); }
我也有点晚了; 但你可以使用这个(C风格的可变参数):
template<typename T> void foreach(std::function<void(T)> callback, int count...) { va_list args; va_start(args, count); for (int i = 0; i < count; i++) { std::vector<T> v = va_arg(args, std::vector<T>); std::for_each(v.begin(), v.end(), callback); } va_end(args); } foreach<int>([](const int &i) { // do something here }, 6, vecA, vecB, vecC, vecD, vecE, vecF);
或者这个(使用函数参数包):
template<typename Func, typename T> void foreach(Func callback, std::vector<T> &v) { std::for_each(v.begin(), v.end(), callback); } template<typename Func, typename T, typename... Args> void foreach(Func callback, std::vector<T> &v, Args... args) { std::for_each(v.begin(), v.end(), callback); return foreach(callback, args...); } foreach([](const int &i){ // do something here }, vecA, vecB, vecC, vecD, vecE, vecF);
或者这个(使用括号括起来的初始化列表):
template<typename Func, typename T> void foreach(Func callback, std::initializer_list<std::vector<T>> list) { for (auto &vec : list) { std::for_each(vec.begin(), vec.end(), callback); } } foreach([](const int &i){ // do something here }, {vecA, vecB, vecC, vecD, vecE, vecF});
或者你可以像这样join向量: 连接两个向量的最好方法是什么? 然后迭代大vector。