Mathai
Mathai

Reputation: 839

Is there a way to iterate over two containers without using two for loops

Is there a way to iterate over two containers (one followed by the other), without using two for loops.

My intention is to do something like this

vector<int> a{ 1,2,3 };
vector<int> b{ 4,5,6 };

auto it = a.begin();
auto end = b.end();

for (; it != end; ++it)
{
    if (it == a.end())
    {
        it = b.begin();
    }
    // do something with *it
}

to print

1 2 3 4 5 6

(of course it doesn't work. The explanation is in this answer )

I do not want to write two for loops and duplicate the code inside the loop. Is there a way to iterate over a followed by b with a single for loop?

The only thing I can think of is either copy/move the second container to the first or create a new vector combining a and b, and then iterate over it. I do not want to do this either, because this will mean expensive copy operations.

Upvotes: 4

Views: 1334

Answers (9)

Vikas Awadhiya
Vikas Awadhiya

Reputation: 308

Not even a single for() loop requires to print these container, If you use std::copy as follows,

#include <iostream>

#include <vector>

#include <iterator>
#include <algorithm>

int main(int , char *[])
{
    std::vector< int> a{ 1, 2, 3};
    std::vector< int> b{ 4, 5, 6};

    std::copy( a.begin(), a.end(), std::ostream_iterator< int>( std::cout, " "));
    std::copy( b.begin(), b.end(), std::ostream_iterator< int>( std::cout, " "));

    std::cout<< std::endl;

    return 0;
}

output : 1 2 3 4 5 6

Using stl library is best option and it is not required to write any code to print a container.

As per your concern I do not want to write two for loops and duplicate the code inside the loop. Is there a way to iterate over a followed by b with a single for loop?

The way to avoiding duplicate code is write functions which can be used from multiple places, for example if you don't want to use std::copy and wants to write your own code to print these containers (which is not recommended) then you can write following function,

template< typename ForwardIterator>
void print( ForwardIterator begin, ForwardIterator end, const std::string& separator)
{
    while( begin != end)
    {
        std::cout<< *begin<< separator;
        ++begin;
    }
}

then call print function,

print( a.begin(), a.end(), std::string( " "));
print( b.begin(), b.end(), std::string( " "));

Upvotes: 0

balki
balki

Reputation: 27674

If you feel like writing your own, the following helps:

template<class ForwardItr>
struct range {
    ForwardItr beg;
    ForwardItr end;
};

template<class ForwardItr, class F>
void concat_ranges(range<ForwardItr> r1, range<ForwardItr> r2, F f) {
    auto run = [&f](range<ForwardItr> r) {
        for(auto itr = r.beg; itr != r.end; ++itr){
            f(*itr);
        }
    };
    run(r1);
    run(r2);
};

Example: https://gcc.godbolt.org/z/8tPArY

Upvotes: 0

Mathai
Mathai

Reputation: 839

Found an easy 'traditional' way to do this.

for (int i = 0; i < 2; i++)
{
    auto it = (i == 0) ? a.begin() : b.begin();
    auto end = (i == 0) ? a.end() : b.end();
    for (; it != end; ++it)
    {
        // do something with *it
    }
}

Upvotes: 1

Sandro
Sandro

Reputation: 2786

one more way to do it using boost range

#include <vector>
#include <iostream>

#include <boost/range.hpp>
#include <boost/range/join.hpp>

int main()
{
  std::vector<int> a{ 1,2,3 };
  std::vector<int> b{ 4,5,6 };

  for(auto& x : boost::join(a, b)) {
      std::cout << x << " ";
  }
  std::cout << std::endl;
}

Upvotes: 3

BiagioF
BiagioF

Reputation: 9705

Boost Range and Standard Library algorithms are solutions which should be prefered because of their better design.

However, just for sake of completeness, if you really want to apply the idea behind your design you can code like the following:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};

for (auto it = v1.begin(); it != v2.end();) {
  if (it == v1.end()) {
    it = v2.begin();
  } else {
  // {
    // use of *it
  // }
    ++it;
  }
}

Live Demo Here

Upvotes: 2

Oliv
Oliv

Reputation: 18041

To get the most optimal code it is preferable to avoid to make unnecessary test at each loop. Since the most efficient code consist in performing two loops, it is possible to get close to that by changing the entire state of the variables that participate in the loop (iterator and sentinel), (I suppose this is how is implemented a concatenated range ... this is how I did):

vector<int> a{ 1,2,3 };
vector<int> b{ 4,5,6 };

auto it = a.begin();
auto end = a.end();

for (;[&](){
         if (it==end){
            if (end==a.end()) {
              it=b.begin();
              end=b.end();
              return true;
              }
            else return false;
            }
          return true;
          }();
    ++it)
{
   //loop content
}

Upvotes: -1

beerboy
beerboy

Reputation: 1294

You can use boost::range::join like so:

#include <boost/range/join.hpp>

...

std::vector<int> a{ 1,2,3 };
std::vector<int> b{ 4,5,6 };

for (auto i : boost::range::join(a, b))
{
    ...
}

Upvotes: 1

Barry
Barry

Reputation: 302698

Using range-v3, your go-to for all things range-related in C++17 or earlier:

for (int i : view::concat(a, b)) {
    std::cout << i << ' ';
}

Upvotes: 8

max66
max66

Reputation: 66190

Well... your error is a double equal where you need a single equal.

I mean, not

if (it == a.end())
{
    it == b.begin();
} //   ^^ Wrong!

but

if (it == a.end())
{
    it = b.begin();
} //   ^ correct

But I don't think it's a good idea: we are sure that a.end() != b.end() ?

Your code depend from this.

Upvotes: -1

Related Questions