44

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)
honk
  • 9,137
  • 11
  • 75
  • 83
declapp auto
  • 668
  • 1
  • 8
  • 19

9 Answers9

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

Community
  • 1
  • 1
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • 1
    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? – Baum mit Augen Jun 11 '15 at 14:00
  • 1
    @BaummitAugen - sure it has - look at `boost::adaptors::filtered`. But for "index view" - probably not (I am not sure)... – PiotrNycz Jun 11 '15 at 14:01
  • 1
    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... – PiotrNycz Jun 12 '15 at 08:41
  • 1
    @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: http://stackoverflow.com/questions/30976131/is-this-possible-to-filter-rangecontainer-by-indexes-not-by-values-to-get-mi – PiotrNycz Jun 22 '15 at 09:17
14

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.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • 2
    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. – Cory Kramer Jun 11 '15 at 13:33
  • @CoryKramer Why can't the latter take advantage of `RandomAccessInterator`? You could even easily implement `std::next` using `std::advance`. – Baum mit Augen Jun 11 '15 at 13:34
  • 2
    @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. – Cory Kramer Jun 11 '15 at 13:44
  • 1
    Still twice slower than alternatives. Not to mention poor readability. – No-Bugs Hare Jun 11 '15 at 13:46
  • 1
    @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. – Baum mit Augen Jun 11 '15 at 13:57
  • Actually, you know what? You're right. I take it all back, having read [this](http://stackoverflow.com/a/15017115/560648). (Though PiotrNycz posted his answer I'm not completely convinced that this one is _fully_ what the OP was after!) – Lightness Races in Orbit Jun 11 '15 at 14:14
  • 1
    *This might run through the range almost twice*: a rather hairy issue for InputIterator (such as `std::cin`). – Matthieu M. Jun 12 '15 at 08:06
  • @MatthieuM. Yep. That's why I wrote that my solutions works for forward iterators. For them it's fine. – Baum mit Augen Jun 12 '15 at 10:40
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;
    }
}
Petr
  • 9,812
  • 1
  • 28
  • 52
  • 2
    -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. – Lightness Races in Orbit Jun 11 '15 at 13:40
  • 1
    In my opinion this solution is better than the .begin() and .end() stuff. Much easier to read, understand and code. – Support Ukraine Jun 11 '15 at 13:47
  • @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. – Petr Jun 11 '15 at 13:47
  • @Petr: I disagree, for the reasons given. – Lightness Races in Orbit Jun 11 '15 at 13:47
  • +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. – Grault Jun 11 '15 at 22:24
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

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • 4
    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. – Daniel Frey Jun 11 '15 at 20:35
  • @DanielFrey you're right. Thanks for pointing that out. Fixed. – Marco A. Jun 12 '15 at 10:45
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.

No-Bugs Hare
  • 1,557
  • 14
  • 15
  • 7
    -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. – Lightness Races in Orbit Jun 11 '15 at 13:40
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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
3

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

honk
  • 9,137
  • 11
  • 75
  • 83
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.

Alexander Revo
  • 719
  • 9
  • 15
  • Same proposal than Marco A's, same issue with InputIterator. – Matthieu M. Jun 12 '15 at 08:09
  • @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](http://www.boost.org/doc/libs/1_58_0/libs/range/doc/html/range/reference/adaptors/reference/sliced.html) wrong, [boost solution](http://stackoverflow.com/a/30782683/3811791) won't work with `InputIterator` either, since it requires `RandomAccessRange`. – Alexander Revo Jun 12 '15 at 14:26
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;
}
ysdx
  • 8,889
  • 1
  • 38
  • 51
  • 1
    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. – Matthieu M. Jun 12 '15 at 08:13
  • I agree that size_t should be used. – ysdx Jun 12 '15 at 08:39
  • 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. – ysdx Jun 12 '15 at 08:40
  • 1
    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. – Matthieu M. Jun 12 '15 at 08:48
  • 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. – ysdx Jun 12 '15 at 12:52