Как я могу перебрать два вектора одновременно, используя BOOST_FOREACH?

Я хотел бы повторить следующее с BOOST FOREACH

std::vector<int>::const_iterator i1;
std::vector<int>::const_iterator i2;
for( i1 = v1.begin(), i2 = v2.begin();
     i1 < v1.end() && i2 < v2.end();
     ++i1, ++i2 )
{
     doSomething( *i1, *i2 );
}

4 ответа

Решение

Итерация по двум вещам одновременно называется "zip" (из функционального программирования), а Boost имеет итератор zip:

Zip-итератор предоставляет возможность параллельной итерации нескольких контролируемых последовательностей одновременно. Zip-итератор состоит из набора итераторов. Перемещение итератора zip перемещает все итераторы параллельно. Разыменование итератора zip возвращает кортеж, содержащий результаты разыменования отдельных итераторов.

Обратите внимание, что это итератор, а не диапазон, поэтому использовать BOOST_FOREACH вам придется набить два из них в iterator_range или pair, Так что это не будет красиво, но с небольшой осторожностью вы, вероятно, сможете придумать zip_range и писать:

BOOST_FOREACH(boost::tuple<int,int> &p, zip_range(v1, v2)) {
    doSomething(p.get<0>(), p.get<1>());
}

Или особый случай для 2 и использования std::pair скорее, чем boost::tuple,

Я полагаю, что с doSomething может иметь параметры (int&, int&)на самом деле мы хотим tuple<int&,int&>, Надеюсь, что это работает.

Если вы используете boost, я думаю, что это должно быть так просто:

#include <boost/foreach.hpp>
#include <boost/range/combine.hpp>
std::vector<int> v1;
std::vector<int> v2;

// iterate over values
int i1, i2;
BOOST_FOREACH(boost::tie(i1, i2), boost::combine(v1, v2))
    std::cout << i1+i2 << "\n"; // sums two vectors

// iterate over references
typedef boost::tuple<int&, int&> int_ref_tuple;
BOOST_FOREACH(int_ref_tuple tup, boost::combine(v1, v2))
    tup.get<0>() = tup.get<1>(); // assigns one vector to another

странная часть в том, что boost:: объединение не задокументировано. У меня все равно работает.

Если вы хотите использовать BOOST_FOREACH для одновременной итерации двух векторов, как вы сделали в своем примере кода, вы должны инкапсулировать оба вектора в класс-оболочку, который должен предоставить begin а также end функции. Эти функции возвращают пользовательский итератор, который будет использоваться для перебора оболочки, который внутренне будет перебирать оба вектора. Звучит не очень хорошо, но это то, что вы должны сделать.

Это моя первая попытка реализовать это (минимальная реализация, просто чтобы продемонстрировать основную идею):

template<typename T>
struct wrapper
{
    struct iterator
    {
         typedef typename std::vector<T>::iterator It;
         It it1, it2;
         iterator(It it1, It it2) : it1(it1), it2(it2) {}
         iterator & operator++()
         {
            ++it1; ++it2; return *this;
         }
         iterator & operator *()
         {
            return *this;
         }
         bool operator == (const iterator &other)
         {
             return !(*this != other);
         }
         bool operator != (const iterator &other)
         {
             return it1 != other.it1 && it2 != other.it2;
         }
    };
    iterator begin_, end_;
    wrapper(std::vector<T> &v1,  std::vector<T> &v2) 
      : begin_(v1.begin(), v2.begin()),end_(v1.end(), v2.end())
    {
    }
    wrapper(const wrapper & other) : begin_(other.begin_), end_(other.end_) {}
    iterator begin() 
    {
          return begin_;
    }
    iterator end() 
    {
          return end_;
    }    
};

И следующий код теста. Так как он использует обычный for цикл, потому что ideone не установлен для boost для C++0x или я делаю что-то не так, когда включаю его.

int main() {
        std::vector<int> v1 = {1,2,3,4,5,6};
        std::vector<int> v2 = {11,12,13,14,15};
        wrapper<int> w(v1,v2);
        for(wrapper<int>::iterator it = w.begin(); it != w.end(); ++it)
        {
             std::cout << *it.it1 <<", "<< *it.it2 << std::endl;
        }
        return 0;
}

Выход:

1, 11
2, 12
3, 13
4, 14
5, 15

Демо: http://ideone.com/Hf667

Это хорошо только для экспериментов и в целях обучения, так как я не претендую на то, что оно идеально. Там может быть много улучшений. И @Steve уже опубликовал решение Boost.

Благодаря ответу Стива Джессопа и замечательным комментариям, я пришел к следующему решению, поэтому, если вам это нравится, сначала проголосуйте за ответ Стива Джессопа.;)

#include <iostream>
#include <vector>

#include <boost/typeof/typeof.hpp>
#include <boost/typeof/std/vector.hpp>

#include <boost/foreach.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/range/iterator_range.hpp>

using namespace boost;

int main(int argc, char **argv) {
    std::vector<int> vecFirst = assign::list_of(1)(2)(3)(43)(7)(13);
    std::vector<double> vecSecond = assign::list_of(53.45)(-23.545)(0.1574)(1.001)(0.0047)(9.7);

    BOOST_AUTO(zipSequence,
       make_iterator_range(
           make_zip_iterator(make_tuple(vecFirst.begin(), vecSecond.begin())), 
           make_zip_iterator(make_tuple(vecFirst.end(), vecSecond.end()))
       )
    );

    BOOST_FOREACH( BOOST_TYPEOF(*zipSequence.begin()) each, zipSequence) {
       std::cout << "First vector value : " << each.get<0>() 
                 << " - Second vector value : " << each.get<1>() 
                 << std::endl;
    }
}
Другие вопросы по тегам