I would like to know if there is a nice way to iterate over at most N elements in a container using the 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).
我想知道是否有一种很好的方法可以使用标准库中基于范围的for循环和/或算法来迭代容器中的大多数N个元素(这就是关键所在,我知道我可以只使用带有条件的“old”for循环)。
Basically, I'm looking for something that corresponds to this Python code:
基本上,我在寻找与这个Python代码对应的东西:
for i in arr[:N]:
print(i)
8 个解决方案
#1
35
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:
正如我个人使用这个或这个答案(两者都+1),只是为了增加您的知识——您可以使用boost适配器。对于你的情况,切片看起来是最合适的:
#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:
一个重要的注意事项:N被分割成不大于距离(范围)——因此更安全(和更慢)的版本如下:
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 ;)
所以-再说一遍-我将使用更简单的旧的C/ c++方法(您希望在您的问题中避免这种方法;)
#2
12
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.
这个可能会经过这个范围两次,但是我没有其他方法来得到这个范围的长度。
#3
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;
}
}
#4
7
C++ is great since you can code your own hideous solutions and hide them under an abstraction layer
c++非常棒,因为您可以编写自己可怕的解决方案,并将它们隐藏在抽象层之下
#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
}
}
生活的例子
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
这是可行的,因为基于范围的for循环产生的代码类似于下面的代码
{
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
病死率。begin_expr和end_expr
#5
6
If your container doesn't have (or might not have) RandomAccessIterator, there is still a way to skin this cat:
如果您的容器没有(或者可能没有)RandomAccessIterator,那么仍然有一种方法可以保护这只猫:
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.
至少对我来说,它是非常易读的:-)。无论容器类型如何,它都具有O(N)复杂度。
#6
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:
它封装一个标量类型T,在*上返回一个副本。有趣的是,它也适用于迭代器,这在这里很有用,因为它让我们可以从指针中有效地继承:
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:
接下来,我们有一个范围类型。sfinaa - modified形式可以在很多地方找到。它使得在for(:)循环中迭代一系列迭代器变得容易:
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:
index_filter_it接受一系列索引和一个随机访问迭代器,并将一系列索引迭代器放入该随机访问迭代器的数据中:
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
:
index_filter接受一个index_range和一个随机访问容器,与它们的索引进行交叉,然后调用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.
中提琴,我们有一个很大的乐器。
生活的例子
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.
将在arr中访问1,3,0,18,22,2,4。但是,它不会有boundcheck,除非arr.begin()[] boundcheck。
There are probably errors in the above code, and you should probably just use boost
.
上面的代码中可能有错误,您应该使用boost。
If you implement -
and []
on indexT
, you can even daisy chain these ranges.
如果你在indexT上实现- and[],你甚至可以用菊花链连接这些范围。
#7
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
:
这个解决方案不会超过end(), std:::list的复杂度为O(N)(不使用std:::distance)与std::for_each兼容,只需要转发器:
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.
它惟一不做的事情是使用诸如std::istream_iterator之类的InputIterator,您将不得不使用外部计数器。
#8
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;
}
#1
35
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:
正如我个人使用这个或这个答案(两者都+1),只是为了增加您的知识——您可以使用boost适配器。对于你的情况,切片看起来是最合适的:
#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:
一个重要的注意事项:N被分割成不大于距离(范围)——因此更安全(和更慢)的版本如下:
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 ;)
所以-再说一遍-我将使用更简单的旧的C/ c++方法(您希望在您的问题中避免这种方法;)
#2
12
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.
这个可能会经过这个范围两次,但是我没有其他方法来得到这个范围的长度。
#3
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;
}
}
#4
7
C++ is great since you can code your own hideous solutions and hide them under an abstraction layer
c++非常棒,因为您可以编写自己可怕的解决方案,并将它们隐藏在抽象层之下
#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
}
}
生活的例子
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
这是可行的,因为基于范围的for循环产生的代码类似于下面的代码
{
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
病死率。begin_expr和end_expr
#5
6
If your container doesn't have (or might not have) RandomAccessIterator, there is still a way to skin this cat:
如果您的容器没有(或者可能没有)RandomAccessIterator,那么仍然有一种方法可以保护这只猫:
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.
至少对我来说,它是非常易读的:-)。无论容器类型如何,它都具有O(N)复杂度。
#6
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:
它封装一个标量类型T,在*上返回一个副本。有趣的是,它也适用于迭代器,这在这里很有用,因为它让我们可以从指针中有效地继承:
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:
接下来,我们有一个范围类型。sfinaa - modified形式可以在很多地方找到。它使得在for(:)循环中迭代一系列迭代器变得容易:
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:
index_filter_it接受一系列索引和一个随机访问迭代器,并将一系列索引迭代器放入该随机访问迭代器的数据中:
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
:
index_filter接受一个index_range和一个随机访问容器,与它们的索引进行交叉,然后调用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.
中提琴,我们有一个很大的乐器。
生活的例子
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.
将在arr中访问1,3,0,18,22,2,4。但是,它不会有boundcheck,除非arr.begin()[] boundcheck。
There are probably errors in the above code, and you should probably just use boost
.
上面的代码中可能有错误,您应该使用boost。
If you implement -
and []
on indexT
, you can even daisy chain these ranges.
如果你在indexT上实现- and[],你甚至可以用菊花链连接这些范围。
#7
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
:
这个解决方案不会超过end(), std:::list的复杂度为O(N)(不使用std:::distance)与std::for_each兼容,只需要转发器:
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.
它惟一不做的事情是使用诸如std::istream_iterator之类的InputIterator,您将不得不使用外部计数器。
#8
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;
}