34

I'd like to replicate the following with 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 );
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
Candy Chiu
  • 6,579
  • 9
  • 48
  • 69
  • 6
    Peccadillo: You probably want to use `!=` instead of `<` in case your container type changes from vector to some other that doesn't support the ranged iterator concept. And you may want to consider what to do if the count of elements is different in each. – Andy Finkenstadt Sep 02 '11 at 17:18
  • Some good answers for this question can also be found here: http://stackoverflow.com/questions/8511035/sequence-zip-function-for-c11 – knedlsepp Nov 30 '15 at 12:37
  • 1
    Possible duplicate of [Sequence-zip function for c++11?](https://stackoverflow.com/questions/8511035/sequence-zip-function-for-c11), as is [What's the best way to iterate over two or more containers simultaneously](https://stackoverflow.com/questions/12552277/whats-the-best-way-to-iterate-over-two-or-more-containers-simultaneously) – underscore_d Sep 22 '18 at 17:13

4 Answers4

35

Iterating over two things simultaneously is called a "zip" (from functional programming), and Boost has a zip iterator:

The zip iterator provides the ability to parallel-iterate over several controlled sequences simultaneously. A zip iterator is constructed from a tuple of iterators. Moving the zip iterator moves all the iterators in parallel. Dereferencing the zip iterator returns a tuple that contains the results of dereferencing the individual iterators.

Note that it's an iterator, not a range, so to use BOOST_FOREACH you're going to have to stuff two of them into an iterator_range or pair. So it won't be pretty, but with a bit of care you can probably come up with a simple zip_range and write:

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

Or special-case for 2 and use std::pair rather than boost::tuple.

I suppose that since doSomething might have parameters (int&, int&), actually we want a tuple<int&,int&>. Hope it works.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 1
    Very nice, didn't know about zip iterator (never needed it :-)). +1 –  Sep 02 '11 at 17:24
  • 3
    @Vlad: neither did I, but as long as you know it's called "zip", and have blind faith that anything you can think of, Boost already has, Google is your friend :-) – Steve Jessop Sep 02 '11 at 17:27
  • I never did functional programming, so I didn't even hear of "zip" (except when we talk compression). So nice to know. –  Sep 02 '11 at 17:29
  • 3
    ..Or he could just `std:make_pair(beg, end)` since BOOST_FOREACH accepts those as ranges too. (where `beg` is `make_zip_iterator(make_tuple(v1.begin(), v2.begin())` etc – Cubbi Sep 02 '11 at 17:31
  • @Cubbi: good point, I forgot that. And `std::make_pair(beg, end);` is slightly shorter than `boost::iterator_range(beg, end);`. – Steve Jessop Sep 02 '11 at 17:37
  • I love the zip idea, and I really wish this becomes part of TR2 or whatever it'll be called. Going from a tuple of iterators to an iterator of tuples is a great tool. – Kerrek SB Sep 02 '11 at 19:44
  • zip exists also in "procedural" languages, notably python : http://stackoverflow.com/questions/13704860/zip-lists-in-python – v.oddou Oct 07 '13 at 07:07
  • @v.oddou: yes, often procedural languages can easily incorporate bits and pieces from functional languages (as Python has done and as Boost has done). – Steve Jessop Oct 07 '13 at 07:40
  • Can you ammend this answer to account for C++11? Or does it stand as is? – einpoklum Dec 20 '13 at 14:07
  • @einpoklum: the question says it wants to use BOOST_FOREACH, so I don't think C++11 is relevant as asked. But if the question said "I want to use a range-based for loop" then the answer would be essentially the same I think. Just use that instead. There's no `std::zip_iterator` in C++11, but there is `std::tuple`. – Steve Jessop Dec 20 '13 at 15:33
16

If you use boost, I think it should be as simple as:

#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

the strange part is that boost::combine is not documented. Works for me, anyway.

panda-34
  • 4,089
  • 20
  • 25
  • 5
    It's documented since boost 1.56: http://www.boost.org/doc/libs/1_56_0/libs/range/doc/html/range/reference/utilities/combine.html – oxygene Oct 30 '14 at 08:14
8

If you want to use BOOST_FOREACH to iterate two vectors simultenously, as you've done in your sample code, then you've to encapsulate both vectors in a wrapper class which should expose begin and end functions. These functions return custom iterator to be used to iterate over the wrapper which internally will iterate over the two vectors. Doesn't sound good, but that is what you've to do.

This is my first attempt to implement this (minimal implementation just to demonstrate the basic idea):

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

And the following is the test code. Since it's using usual for loop, because ideone has not installed for boost for C++0x or I'm doing something wrong when including it.

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

Output:

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

Demo : http://ideone.com/Hf667

This is good for experimentation and learning purpose only, as I don't claim it to be perfect. There can be lots of improvement. And @Steve already has posted boost's solution.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
4

Thanks to the answer of Steve Jessop and the great comments, I came up to the following solution, so if you find that nice, vote up Steve Jessop answer first. ;)

#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;
    }
}
Gustavo Muenz
  • 9,278
  • 7
  • 40
  • 42
daminetreg
  • 9,724
  • 1
  • 23
  • 15