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


113

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

1
现在的transform礼物#include <algorithm>呢?
Ankit Acharya

关于分配循环:如果两者都是向量或相似向量,请使用containerA = containerB;代替循环。
埃莱2015年


Answers:


52

聚会晚了。但是:我会遍历索引。但是不使用经典for循环,而是使用基于范围for的索引循环:

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

indices是一个简单的包装函数,它返回索引的(延迟计算)范围。由于实现(虽然很简单)太长了,无法在此处发布,因此可以在GitHub上找到实现

此代码与使用手动经典循环一样有效for

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

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

2
与boosting count_range相比,您的索引实现有什么优势吗?一个人可以简单地使用boost::counting_range(size_t(0), containerA.size())
SebastianK

3
@SebastianK在这种情况下最大的区别是语法:客观上,我的(我声称)更好。此外,您可以指定步长。有关示例,请参见链接的Github页面,尤其是README文件。
康拉德·鲁道夫2014年

您的想法非常好,我仅在看到它之后才想到使用count_range:clear upvote :)但是,我想知道它是否为(重新)实现这一目标提供了额外的价值。例如,关于性能。当然,更好的语法,我同意,但是编写一个简单的生成器函数来弥补这一缺点就足够了。
SebastianK 2014年

@SebastianK我承认当我编写代码时,我认为它很简单,可以在不使用库的情况下孤立地生活(这是事实!)。现在,我可能会将其写为Boost.Range的包装。也就是说,我的库的性能已经达到最佳。我的意思是,使用indices实现产生的编译器输出使用手动for循环相同。没有任何开销。
康拉德·鲁道夫

因为无论如何我都使用boost,所以在我的情况下会更简单。我已经在boost范围内编写了这个包装器:我只需要一个带有一行代码的函数。但是,如果升压范围的性能也达到最佳,我会很感兴趣。
SebastianK 2014年

38

对于您的特定示例,只需使用

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

对于更一般的情况,您可以使用Boost.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在他们的命名空间功能。同样,这将允许用户const通过zip_c...功能专门访问。

而且,如果您像我一样倡导漂亮的错误消息,那么您可能需要this,它检查是否有任何临时容器传递给任何zip_...函数,如果是,则打印一条漂亮的错误消息。


1
谢谢!但是,有一个问题,为什么您使用auto &&,这意味着&&是什么?
memecs's

@memecs:我建议您通读这个问题以及我的答案,这有点解释了演绎和参考折叠的过程。请注意,其auto工作原理与模板参数完全相同,并且T&&在模板中是通用引用,如第一个链接中所述,因此auto&& v = 42将推导为int&&auto&& w = v;然后推导为int&。它允许您匹配左值和右值,并使它们可变,而无需复制。
Xeo 2012年

@Xeo:但是在foreach循环中,auto &&与auto&相比有什么优势?
Viktor Sehr 2013年

@ViktorSehr:它允许您绑定到临时元素,例如由产生的元素zip_range
Xeo 2013年

23
@Xeo所有到示例的链接都已断开。
kynan 2015年

34

我不知道为什么没人提到这个:

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语句内。


9

如表头中提供的,有很多方法可以对多个容器执行特定的操作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;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

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

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


好像您必须在循环之前声明迭代器?我试过了:for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)但是编译器大喊。谁能解释为什么这是无效的?
大卫·多里亚

@DavidDoria for循环的第一部分是单个语句。您不能在同一条语句中声明两个不同类型的变量。想一想为什么for (int x = 0, y = 0; ...行得通,但for (int x = 0, double y = 0; ...)行不通。
wjl

1
..但是,您可以使用std :: pair <Container1 :: iterator,Container2 :: iterator>其= {c1.begin(),c2.begin()};
lorro

1
还有一点要注意的是,这可以很容易地取得可变参数与C ++ 14倍的typename...
WJL

8

如果仅需要同时在2个容器上进行迭代,则Boost范围库中有一个标准的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;
}

当您需要使用一种算法处理两个以上的容器时,则需要使用zip。


精彩!你怎么找到的?好像它没有记录在任何地方。
米哈伊尔

4

另一个解决方案是在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然后将其递增。这很容易扩展到多个容器的情况。


3

范围库提供此功能和其他非常有用的功能。以下示例使用Boost.Range埃里克·尼布勒(Eric Niebler)的rangev3应该是一个不错的选择。

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

该程序未使用g ++ 4.8.0进行编译。 delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^
syam

将std :: tie更改为boost:tie之后,它进行了编译。
syam '16

对于结构化绑定的版本,我得到以下编译错误(使用MSVC 19.13.26132.0和Windows SDK版本10.0.16299.0): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)
pooya13

结构化绑定似乎无法使用boost::combinestackoverflow.com/q/55585723/8414561
Dev Null

2

我也来晚了 但是您可以使用此(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});

或者,您也可以像下面这样加入向量:连接两个向量的最佳方法是什么?然后遍历大向量。


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);    
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.