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

11

45

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)
Gift answered 11/6, 2015 at 13:11 Comment(7)
@DavidHaim What exactly is confusing about "at most N elements"?Bellini
@DavidHaim It means that I would like to iterate over all elements in a container if its size is less or equal to N and over N elements otherwise.Gift
@DavidHaim "at most N" -> c.size() < N ? c.size() : NBellini
@DavidHaim: Perhaps you can further explain your confusion then, because the goal is clearly and unambiguously stated, and everybody else seems to get it!Mariannmarianna
@MarcoA.: Is it deliberate that below that code is the compiler output showing that it doesn't work? ;)Mariannmarianna
@LightnessRacesinOrbit damn no.. sorry.. started fiddling in the same link :/Whitefaced
With range-v3, for (auto&& e: input | ranges::view::take(N)) { /* stuff */ }.Amygdalate
U
4

Since C++20 you can add the range adaptor std::views::take from the Ranges library to your range-based for loop. This way you can implement a similar solution to the one in PiotrNycz's answer, but without using Boost:

int main() {
    std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9};
    const int N = 4;

    for (int i : v | std::views::take(N))
        std::cout << i << std::endl;
        
    return 0;
}

The nice thing about this solution is that N may be larger than the size of the vector. This means, for the example above, it is safe to use N = 13; the complete vector will then be printed.

Code on Wandbox

Undershorts answered 11/3, 2021 at 13:24 Comment(0)
M
39

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

Meggy answered 11/6, 2015 at 13:48 Comment(4)
This is really neat! Does Boost also have some kind of array view that can give me only the elements that match a predicate or based on some index list?Candis
@BaummitAugen - sure it has - look at boost::adaptors::filtered. But for "index view" - probably not (I am not sure)...Meggy
Aside note: I am not really sure it is "so much" slower - good compiler with high optimization level should be able to generate similar binaries...Meggy
@BaummitAugen A few days later after your comment I encountered a real-world problem which requires to have such index view as you mentioned - and I managed to find such index view solution - so I posted on SO in Q/A format: #30976631Meggy
C
15

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.

Candis answered 11/6, 2015 at 13:31 Comment(8)
I would suggest std::advance(begin, N) instead of std::next. The former may take advantage of RandomAccessInterator if it is available, the latter will not.Faunus
@CoryKramer Why can't the latter take advantage of RandomAccessInterator? You could even easily implement std::next using std::advance.Candis
@BaummitAugen Looks like I lied, from the standard § 24.4.4.6 for std::next() "Effects: Equivalent to advance(x, n); return x;" I'm not sure that it is a requirement to take advantage of RandomAccessIterator, but it would be a shame if they didn't.Faunus
Still twice slower than alternatives. Not to mention poor readability.Empanel
@LightnessRacesinOrbit I used std::next because I want the n-th successor of a given iterator, which is exactly what std::next is there for.Candis
Actually, you know what? You're right. I take it all back, having read this. (Though PiotrNycz posted his answer I'm not completely convinced that this one is fully what the OP was after!)Mariannmarianna
This might run through the range almost twice: a rather hairy issue for InputIterator (such as std::cin).Furtado
@MatthieuM. Yep. That's why I wrote that my solutions works for forward iterators. For them it's fine.Candis
I
8

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;
    }
}
Impudicity answered 11/6, 2015 at 13:23 Comment(5)
-1: The question explicitly states he already knows how to do this with his own for loop. I realise he asks for ranged-for ideas as well, but your suggestion really doesn't add anything specific to ranged-for. He wants to adapt the standard algorithms, like std::for_each. That'll probably involve futzing with iterators.Mariannmarianna
In my opinion this solution is better than the .begin() and .end() stuff. Much easier to read, understand and code.Nickens
@LightnessRacesinOrbit, I think in this case the OP should clarify his request in more details. Personally I treat the question as "what is the simplest way from the point of coding": just like range-based loop replaced the equivalent loop with iterators, the OP might want to make his code as clear as possible. Anyway, my answer matched the question in its current wording.Impudicity
@Petr: I disagree, for the reasons given.Mariannmarianna
+1 "Range-based for and/or algorithms from the standard library" doesn't require std:: algorithms, and I like the simplicity here. Libraries are overkill, like a sledgehammer on a fly when you have a proper flyswatter anyway.Jornada
W
7

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

Whitefaced answered 11/6, 2015 at 14:11 Comment(2)
Your code is illegal, the range(a) is a temporary, till() returns a reference to it and that reference is bound in the range-based for loop (auto && __range = range_expression). The intermediate temporaries in the expression are then deleted before the loop is executed - you end up with a dangling reference.Hampshire
@DanielFrey you're right. Thanks for pointing that out. Fixed.Whitefaced
E
6

If your container doesn't have (or might not have) RandomAccessIterator, there is still a way to skin this cat:

int cnt = 0;
for(auto it=container.begin(); it != container.end() && cnt < N ; ++it,++cnt) {
  //
}

At least for me, it is very readable :-). And it has O(N) complexity regardless of container type.

Empanel answered 11/6, 2015 at 13:34 Comment(1)
-1: The question explicitly states he already knows how to do this with his own for loop. He wants to adapt the standard algorithms, like std::for_each. That'll probably involve futzing with iterators.Mariannmarianna
N
5

This is an index iterator. Mostly boilerplate, leaving it out, because I'm lazy.

template<class T>
struct indexT
 //: std::iterator< /* ... */ > // or do your own typedefs, or don't bother
{
  T t = {};
  indexT()=default;
  indexT(T tin):t(tin){}
  indexT& operator++(){ ++t; return *this; }
  indexT operator++(int){ auto tmp = *this; ++t; return tmp; }
  T operator*()const{return t;}
  bool operator==( indexT const& o )const{ return t==o.t; }
  bool operator!=( indexT const& o )const{ return t!=o.t; }
  // etc if you want full functionality.
  // The above is enough for a `for(:)` range-loop
};

it wraps a scalar type T, and on * returns a copy. It also works on iterators, amusingly, which is useful here, as it lets us inherit effectively from a pointer:

template<class ItA, class ItB>
struct indexing_iterator:indexT<ItA> {
  ItB b;
  // TODO: add the typedefs required for an iterator here
  // that are going to be different than indexT<ItA>, like value_type
  // and reference etc.  (for simple use, not needed)
  indexing_iterator(ItA a, ItB bin):ItA(a), b(bin) {}
  indexT<ItA>& a() { return *this; }
  indexT<ItA> const& a() const { return *this; }
  decltype(auto) operator*() {
    return b[**a()];
  }
  decltype(auto) operator->() {
    return std::addressof(b[**a()]);
  }
};

The indexing iterator wraps two iterators, the second of which must be random-access. It uses the first iterator to get an index, which it uses to look up a value from the second.

Next, we have is a range type. A SFINAE-improved one can be found many places. It makes iterating over a range of iterators in a for(:) loop easy:

template<class Iterator>
struct range {
  Iterator b = {};
  Iterator e = {};
  Iterator begin() { return b; }
  Iterator end() { return e; }
  range(Iterator s, Iterator f):b(s),e(f) {}
  range(Iterator s, size_t n):b(s), e(s+n) {}
  range()=default;
  decltype(auto) operator[](size_t N) { return b[N]; }
  decltype(auto) operator[] (size_t N) const { return b[N]; }\
  decltype(auto) front() { return *b; }
  decltype(auto) back() { return *std::prev(e); }
  bool empty() const { return begin()==end(); }
  size_t size() const { return end()-begin(); }
};

Here are helpers to make working with ranges of indexT easy:

template<class T>
using indexT_range = range<indexT<T>>;
using index = indexT<size_t>;
using index_range = range<index>;

template<class C>
size_t size(C&&c){return c.size();}
template<class T, std::size_t N>
size_t size(T(&)[N]){return N;}

index_range indexes( size_t start, size_t finish ) {
  return {index{start},index{finish}};
}
template<class C>
index_range indexes( C&& c ) {
  return make_indexes( 0, size(c) );
}
index_range intersect( index_range lhs, index_range rhs ) {
  if (lhs.b.t > rhs.e.t || rhs.b.t > lhs.b.t) return {};
  return {index{(std::max)(lhs.b.t, rhs.b.t)}, index{(std::min)(lhs.e.t, rhs.e.t)}};
}

ok, almost there.

index_filter_it takes a range of indexes and a random access iterator, and makes a range of indexed iterators into that random access iterator's data:

template<class R, class It>
auto index_filter_it( R&& r, It it ) {
  using std::begin; using std::end;
  using ItA = decltype( begin(r) );
  using R = range<indexing_iterator<ItA, It>>;
  return R{{begin(r),it}, {end(r),it}};
}

index_filter takes an index_range and a random access container, intersects their indexes, then calls index_filter_it:

template<class C>
auto index_filter( index_range r, C& c ) {
  r = intersect( r, indexes(c) );
  using std::begin;
  return index_filter_it( r, begin(c) );
}

and now we have:

for (auto&& i : index_filter( indexes(0,6), arr )) {
}

and viola, we have a large musical instrument.

live example

Fancier filters are possible.

size_t filter[] = {1,3,0,18,22,2,4};
using std::begin;
for (auto&& i : index_filter_it( filter, begin(arr) ) )

will visit 1, 3, 0, 18, 22, 2, 4 in arr. It does not, however, bounds-check, unless arr.begin()[] bounds-checks.

There are probably errors in the above code, and you should probably just use boost.

If you implement - and [] on indexT, you can even daisy chain these ranges.

Nupercaine answered 11/6, 2015 at 18:56 Comment(0)
U
4

Since C++20 you can add the range adaptor std::views::take from the Ranges library to your range-based for loop. This way you can implement a similar solution to the one in PiotrNycz's answer, but without using Boost:

int main() {
    std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9};
    const int N = 4;

    for (int i : v | std::views::take(N))
        std::cout << i << std::endl;
        
    return 0;
}

The nice thing about this solution is that N may be larger than the size of the vector. This means, for the example above, it is safe to use N = 13; the complete vector will then be printed.

Code on Wandbox

Undershorts answered 11/3, 2021 at 13:24 Comment(0)
B
2

This solution doesn't go past end(), has O(N) complexity for std::list (doesn't use std::distance) works with std::for_each, and only requires ForwardIterator:

std::vector<int> vect = {1,2,3,4,5,6,7,8};

auto stop_iter = vect.begin();
const size_t stop_count = 5;

if(stop_count <= vect.size())
{
    std::advance(stop_iter, n)
}
else
{
    stop_iter = vect.end();
}

std::for_each(vect.vegin(), stop_iter, [](auto val){ /* do stuff */ });

The only thing it doesn't do is work with InputIterator such as std::istream_iterator - you'll have to use external counter for that.

Bateau answered 11/6, 2015 at 13:51 Comment(2)
Same proposal than Marco A's, same issue with InputIterator.Furtado
@MatthieuM. Technically, that would make his solution same as mine, since mine was posted earlier. Anyway, his solution also provides a wrapper to use if range-based for loop, so they're not the same. Also, unless I interpret boost documentation wrong, boost solution won't work with InputIterator either, since it requires RandomAccessRange.Bateau
O
2

First we write an iterator which stops at a given index:

template<class I>
class at_most_iterator
  : public boost::iterator_facade<at_most_iterator<I>,
                  typename I::value_type,
                  boost::forward_traversal_tag>
{
private:
  I it_;
  int index_;
public:
  at_most_iterator(I it, int index) : it_(it), index_(index) {}
  at_most_iterator() {}
private:
  friend class boost::iterator_core_access;

  void increment()
  {
    ++it_;
    ++index_;
  }
  bool equal(at_most_iterator const& other) const
  {
    return this->index_ == other.index_ || this->it_ == other.it_;
  }
  typename std::iterator_traits<I>::reference dereference() const
  {
    return *it_;
  }
};

We can now write an algorithme for making a rage of this iterator from a given range:

template<class X>
boost::iterator_range<
  at_most_iterator<typename X::iterator>>
at_most(int i, X& xs)
{
  typedef typename X::iterator iterator;
  return std::make_pair(
            at_most_iterator<iterator>(xs.begin(), 0),
            at_most_iterator<iterator>(xs.end(), i)
        );
}

Usage:

int main(int argc, char** argv)
{
  std::vector<int> xs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  for(int x : at_most(5, xs))
    std::cout << x << "\n";
  return 0;
}
Orran answered 12/6, 2015 at 8:2 Comment(5)
Your equal method is bothering me. I understand why you use a ||, however I can think of issues with cyclic iterators (for example). I would propose to only refer to index_ there, and not bothering with the iterators at all. Also (nit), don't use int for index_, prefer something like size_t as int could be as small as 16 bits for example.Furtado
I agree that size_t should be used.Orran
If you do not compare the iterator, the code will break if the number of elements in the original range is lower than what we ask.Orran
Indeed. But || this->it_ == other.it_ seems to be the wrong solution as it breaks a cycling iterator (and yes, the iterator pair concept in C++ makes things harder, a single object would be all too easy). I wonder if sliced in Boost adaptors handles cycling iterators.Furtado
Yes, having to use a pair-of-external-iterator makes this thing harfer than it should. I'm not so sure about what this code breaks w.r.t. a cycling iterator however.Orran
U
0

Since Ranges became part of C++ (in C++20), the way to achieve this is with a view adapter:

   container | std::views::take(N)

As the view is a range in its own right, we can use that in a for loop, or pass it to algorithms.

For example, the Python loop in the question is expressed as

   for (auto const& i: arr | std::views::take(N)) {
        std::cout << i << '\n';
   }

Or, without the explicit loop:

   using value_type = typename decltype(arr)::value_type;
   auto print = std::ostream_iterator<value_type>{std::cout, '\n'};

   std::ranges::copy(arr | std::views::take(N), print);
Underachieve answered 15/10, 2023 at 8:49 Comment(0)
S
-1

My favourite answer to this (now that we have C++20), is like this:

for (const auto& foo : std::span{bar.begin(), num_iterations}) {
    // stuff
}

Using a span like this, we can iterate over any subsequence easily, using a range-based for:

for (const auto& foo : std::span{bar.begin() + subsequence_start, num_iterations}) {
    // stuff
}
Sporades answered 9/10, 2023 at 23:35 Comment(2)
Not quite - that works over exactly N elements, even if that's more than there are in the container. And it most container types aren't convertible to std::span anyway. The real answer is for (const auto& foo: container | std::views::take(N))Underachieve
The point about indexing past the end of the range is fair in the sense that it's not equivalent to the python slice example, but if that's a concern you can easily add a std::min call. The python slice is also not really equivalent to "most container types", so that's not really a point. I'm going to have to strongly disagree that that's the "real" answerSporades

© 2022 - 2024 — McMap. All rights reserved.