同时迭代两个或多个容器的最佳方法是什么

C + + 11提供了多种遍历容器的方法,例如:

基于范围的循环

for(auto c : container) fun(c)

For _ each

for_each(container.begin(),container.end(),fun)

然而,在两个(或多个)相同大小的容器上迭代以完成以下任务的推荐方法是什么:

for(unsigned i = 0; i < containerA.size(); ++i) {
containerA[i] = containerB[i];
}
133025 次浏览

有很多方法可以使用 algorithm报头中提供的多个容器执行 具体的事情。例如,在您给出的示例中,您可以使用 std::copy而不是显式的 For 循环。

另一方面,除了普通的 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;
Container2::iterator i2;
for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
f(i1, i2);
}
}

显然,您可以以类似的方式制定任何类型的迭代策略。

当然,您可能会争辩说,直接执行内部 for 循环比编写这样的自定义函数更容易... ... 如果只执行一次或两次,那么您是对的。但好的一面是,这是非常可重复使用的。=)

对于您的具体示例,只需使用

std::copy_n(contB.begin(), contA.size(), contA.begin())

对于更一般的情况,您可以使用 Boost。迭代器的 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函数。此外,这将允许用户通过 zip_c...函数专门获得 const访问权限。

如果您像我一样提倡好的错误消息,那么您可能需要 这个,它检查是否有任何临时容器被传递给任何 zip_...函数,如果有,则打印一个好的错误消息。

这么晚才来派对。但是: 我会遍历索引。但不是经典的 for循环,而是指数上基于范围的 for循环:

for(unsigned i : indices(containerA)) {
containerA[i] = containerB[i];
}

indices是一个简单的包装函式,它为索引返回一个(延迟计算的)范围。由于实现——尽管很简单——在这里发布它有点太长了,您可以在 GitHub 上找到一个实现

这段代码是 效率一样高,使用的是手动的、经典的 for循环。

如果这种模式经常出现在你的数据中,考虑使用另一种模式,zips 两个序列并产生一系列元组,对应于成对的元素:

for (auto& [a, b] : zip(containerA, containerB)) {
a = b;
}

zip的实现留给读者作为练习,但它很容易跟随 indices的实现。

(在 C + + 17之前,你必须编写以下代码:)

for (auto&& items : zip(containerA, containerB))
get<0>(items) = get<1>(items);

如果只需要同时迭代两个容器,那么在升级范围库中有一个扩展版本的 standard for _ each 算法,例如:

#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;
}

当您需要在一个算法中处理多于2个容器时,那么您需要使用 zip。

我想知道为什么没有人提到这一点:

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 块中。

另一种解决方案是在 lambda 中捕获另一个容器的迭代器的引用,并对此使用 post 增量运算符。举例来说,简单的副本如下:

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做任何事情,然后增加它。

一个范围库提供了这个和其他非常有用的功能。

#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});

或者你可以像这样把向量连接起来: 连接两个向量的最佳方法是什么?,然后在大向量上迭代。

如果可能的话,我个人更喜欢使用 STL 中已有的内容(在 <algorithm>头文件中)。std::transform有一个可以使用两个输入迭代器的签名。因此,至少在两个输入容器的情况下,您可以这样做:

std::transform(containerA.begin(), containerA.end(), containerB.begin(), outputContainer.begin(), [&](const auto& first, const auto& second){
return do_operation(first, second);
});

注意,outputContainer也可以是输入容器之一。但是一个限制是,如果要在适当的位置修改一个容器,则不能执行更新后操作。

答案就在这里! ... ... 当 C + + 23出现的时候。

#include <algorithm>
#include <forward_list>
#include <ranges>
#include <array>
#include <iostream>


int main()
{
auto foos = std::to_array({ 1, 2, 3, 4, 5  });
auto woos = std::to_array({ 6, 7, 8, 9, 10 });


auto fooswoos = std::views::zip(foos,woos);


for(auto [foo, woo] : fooswoos) {
woo += foo;
}
std::ranges::for_each(woos, [](const auto& e) { std::cout << e << '\n'; });


return 0;
}

发生什么事了?

我们正在构建一个特殊的“视图”。这个视图允许我们将容器看作是其他结构,而不需要进行任何复制或类似的操作。使用 结构化绑定结构化绑定,我们能够在每次迭代中获得对每个对齐元素的引用,并且可以对它进行任何我们想要的操作(并且是安全的)

现在就在编译器浏览器上查看它!