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

时间:2012-09-23 12:13:12

标签: c++ c++11 iterator containers

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

10 个答案:

答案 0 :(得分:42)

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

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

indices是一个简单的包装函数,它返回索引的(延迟评估的)范围。由于实现 - 虽然简单 - 在这里发布它有点太长了you can find an implementation on 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);

答案 1 :(得分:37)

对于您的具体示例,请使用

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

Live example.

但是,对于完整的通用性,您可能需要更像this的内容,它可以正常用于没有成员begin() / {{1}的数组和用户定义类型但是 do 在其命名空间中有end() / begin个函数。此外,这将允许用户通过end函数专门获得const访问权限。

如果你是像我这样的好错误消息的拥护者,那么你可能需要this,它检查是否有任何临时容器被传递给任何zip_c...函数,并打印出一个如果是这样的错误消息。

答案 2 :(得分:28)

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

fun countEvents(sc: EventSearchCriteria?): Long {
    return trywr(connection.prepareStatement("SELECT COUNT(*) FROM event")) {
        var rs = it.executeQuery()
        rs.next()
        rs.getLong(1)
    }
}

PS:如果容器大小不匹配,那么您必须将代码放在if语句中。

答案 3 :(得分:9)

algorithm标题中提供了多种方法可以使用多个容器执行特定事项。例如,在您给出的示例中,您可以使用std::copy而不是显式for循环。

另一方面,除了普通for循环之外,没有任何内置方法可以一般地迭代多个容器。这并不令人惊讶,因为有一些很多的迭代方法。想一想:你可以用一步迭代一个容器,用另一个步骤迭代一个容器;或者通过一个容器直到它到达终点然后在你进入另一个容器的末端时开始插入;或者每次完全通过另一个容器时第一个容器的一步然后重新开始;或其他一些模式;或一次超过两个容器;等...

但是,如果你想让你的拥有&#34; for_each&#34;样式函数只迭代两个容器,最长可达最短的容器,你可以这样做:

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循环比编写这样的自定义函数更容易......如果您只打算执行一次或两次,那么您是对的。但好的是,这是非常可重用的。 =)

答案 4 :(得分:8)

如果您只需要同时迭代2个容器,则在增强范围库中有一个扩展版本的标准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。

答案 5 :(得分:2)

另一个解决方案可能是捕获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做任何事情,然后递增它。这很容易扩展到多个容器盒。

答案 6 :(得分:1)

范围库提供了此功能和其他非常有用的功能。以下示例使用Boost.RangeEric Niebler's 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;
}

答案 7 :(得分:1)

如果可能的话,我个人更喜欢使用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也可以是输入容器之一。但是一个限制是,如果您要修改其中一个容器,则无法执行更新后操作。

答案 8 :(得分: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);    
    }

答案 9 :(得分:0)

我也有点晚了;但你可以使用它(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});

或者你可以加入像这里的矢量:What is the best way to concatenate two vectors?然后迭代大矢量。