4

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.

Mathai
  • 839
  • 1
  • 12
  • 24
  • 1
    How about writing two loops (or using a standard algorithm, like `std::for_each`) and implementing the duplicate bahaviour in a single function that will be passed to said algorithm? – Fureeish Jan 23 '19 at 21:50
  • @Fureeish Yes, thats a possiblity – Mathai Jan 23 '19 at 21:51
  • Someone care to explain why the question was downvoted? Did my research, and this is not a homework question. – Mathai Jan 23 '19 at 21:52
  • Then I'd go with that approach. Forcing the logic to be enclosed in a single loop will introduce a lot of unnecessary checks and hard-to-read code noise. Prefer simplicity over complexity, especially if the behaviour **and** performance are either the same, or better regarding the simpler approach. – Fureeish Jan 23 '19 at 21:54

9 Answers9

8

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 << ' ';
}
Barry
  • 286,269
  • 29
  • 621
  • 977
3

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;
}
Sandro
  • 2,707
  • 2
  • 20
  • 30
2

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

BiagioF
  • 9,368
  • 2
  • 26
  • 50
  • With visual studio 2017, I get the error "vector iterators incompatible". I guess it is because we compare it against v2.end() when v1 it is v1 iterator. – Mathai Jan 24 '19 at 14:36
  • If both v1 and v2 are empty, you'll need some extra checks. – AndyG Jan 25 '19 at 15:13
  • @AndyG nope. The code checks all cases. I don't think there is any UB here – BiagioF Jan 27 '19 at 19:06
  • @BiagioFesta: You're right, apologies. I misread the `use of *it` as outside the `else` – AndyG Jan 28 '19 at 00:49
1

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))
{
    ...
}
beerboy
  • 1,304
  • 12
  • 12
1

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
    }
}
Mathai
  • 839
  • 1
  • 12
  • 24
0

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

balki
  • 26,394
  • 30
  • 105
  • 151
0

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( " "));
Vikas Awadhiya
  • 290
  • 1
  • 8
-1

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.

max66
  • 65,235
  • 10
  • 71
  • 111
-1

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
}
Oliv
  • 17,610
  • 1
  • 29
  • 72