Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to iterate over at most N elements using range-based for loop?

Tags:

c++

c++11

stl

c++14

Is there a nice way to iterate over at most N elements in a container using a range-based for loop and/or algorithms from the standard library (that's the whole point, I know I can just use the "old" for loop with a condition).

Basically, I'm looking for something that corresponds to this Python code:

for i in arr[:N]:
    print(i)
like image 478
December Avatar asked Jun 11 '15 13:06

December


People also ask

How does range-based for loop work?

Range-based for loop in C++ Range-based for loop in C++ is added since C++ 11. It executes a for loop over a range. Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.

Are range-based for loops faster?

Range-for is as fast as possible since it caches the end iterator[citationprovided], uses pre-increment and only dereferences the iterator once. Then, yes, range-for may be slightly faster, since it's also easier to write there's no reason not to use it (when appropriate).

How do range-based for loops work C++?

C++11 introduced the ranged for loop. This for loop is specifically used with collections such as arrays and vectors. Here, the ranged for loop iterates the array num from beginning to end. The int variable var stores the value of the array element in each iteration.

What does auto do in a for loop?

Range-based for loop in C++ Often the auto keyword is used to automatically identify the type of elements in range-expression.


4 Answers

As I personally would use either this or this answer (+1 for both), just for increasing your knowledge - there are boost adapters you can use. For your case - the sliced seems the most appropriate:

#include <boost/range/adaptor/sliced.hpp>
#include <vector>
#include <iostream>

int main(int argc, const char* argv[])
{
    std::vector<int> input={1,2,3,4,5,6,7,8,9};
    const int N = 4;
    using boost::adaptors::sliced;
    for (auto&& e: input | sliced(0, N))
        std::cout << e << std::endl;
}

One important note: N is required by sliced to be not greater than distance(range) - so safer(and slower) version is as follows:

    for (auto&& e: input | sliced(0, std::min(N, input.size())))

So - once again - I would use simpler, old C/C++ approach (this you wanted to avoid in your question ;)

like image 61
PiotrNycz Avatar answered Oct 02 '22 16:10

PiotrNycz


Here is the cheapest save solution that works for all forward iterators I could come up with:

auto begin = std::begin(range);
auto end = std::end(range);
if (std::distance(begin, end) > N)
    end = std::next(begin,N);

This might run through the range almost twice, but I see no other way to get the length of the range.

like image 28
Baum mit Augen Avatar answered Oct 02 '22 15:10

Baum mit Augen


You can use the good old break to manually break a loop when needed. It works even with range based loop.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> a{2, 3, 4, 5, 6};
    int cnt = 0;
    int n = 3;
    for (int x: a) {
       if (cnt++ >= n) break;
       std::cout << x << std::endl;
    }
}
like image 8
Petr Avatar answered Oct 02 '22 17:10

Petr


C++ is great since you can code your own hideous solutions and hide them under an abstraction layer

#include <vector>
#include <iostream>

//~-~-~-~-~-~-~- abstraction begins here ~-~-~-~-~-//
struct range {
 range(std::vector<int>& cnt) : m_container(cnt),
   m_end(cnt.end()) {}
 range& till(int N) {
     if (N >= m_container.size())
         m_end = m_container.end();
     else
        m_end = m_container.begin() + N;
     return *this;
 }
 std::vector<int>& m_container;
 std::vector<int>::iterator m_end;
 std::vector<int>::iterator begin() {
    return m_container.begin();
 }
 std::vector<int>::iterator end() {
    return m_end;
 }
};
//~-~-~-~-~-~-~- abstraction ends here ~-~-~-~-~-//

int main() {
    std::vector<int> a{11, 22, 33, 44, 55};
    int n = 4;

    range subRange(a);        
    for ( int i : subRange.till(n) ) {
       std::cout << i << std::endl; // prints 11, then 22, then 33, then 44
    }
}

Live Example

The above code obviously lacks some error checking and other adjustments, but I wanted to just express the idea clearly.

This works since range-based for loops produce code similar to the following

{
  auto && __range = range_expression ; 
  for (auto __begin = begin_expr,
       __end = end_expr; 
       __begin != __end; ++__begin) { 
    range_declaration = *__begin; 
    loop_statement 
  } 
} 

cfr. begin_expr and end_expr

like image 7
Marco A. Avatar answered Oct 02 '22 17:10

Marco A.