是否有一种好的方式使用基于范围的for
循环和/或标准库中的算法迭代最多N个容器元素(这就是全部意义,我知道可以使用“旧”的for
循环加条件来解决)。
基本上,我正在寻找与此Python代码相对应的内容:
for i in arr[:N]:
print(i)
就我个人而言,我会使用这个或者这个答案(两个都点赞),仅仅是为了增加你的知识——你可以使用boost适配器。对于你的情况,sliced似乎是最合适的:
#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;
}
一个重要的提示:在sliced
中,N需要小于distance(range)
- 因此更安全(但速度较慢)的版本如下:
一个重要的提示:在sliced
中,N需要小于distance(range)
- 因此更安全(但速度较慢)的版本如下:
for (auto&& e: input | sliced(0, std::min(N, input.size())))
所以 - 再一次 - 我会使用更简单、老式的C/C++方法(这是你在问题中想要避免的 ;))
boost::adaptors::filtered
。但是对于“索引视图”- 可能没有(我不确定)... - PiotrNycz以下是我能想到适用于所有前向迭代器的最便宜的保存解决方案:
auto begin = std::begin(range);
auto end = std::end(range);
if (std::distance(begin, end) > N)
end = std::next(begin,N);
这可能会使范围运行近乎两次,但我看不到其他获取范围长度的方法。
std::advance(begin, N)
而不是std::next
。如果可用,前者可以利用RandomAccessInterator
,而后者则不能。 - Cory Kramer§ 24.4.4.6
中对于 std::next()
的描述 *"Effects: Equivalent to advance(x, n); return x;"*,我不确定是否有必要利用 RandomAccessIterator,但如果不这样做的话,那就太可惜了。 - Cory Kramerstd::next
是因为我想要给定迭代器的第 n 个后继,这正是 std::next
的作用。 - Baum mit Augenstd::cin
)而言,这是一个相当棘手的问题。 - Matthieu M.在需要时,您可以使用经典的break
手动中断循环。它甚至适用于基于范围的循环。
#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;
}
}
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
}
}
上述代码显然缺乏一些错误检查和其他调整,但我只想清楚地表达这个想法。
这是因为基于范围的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
如果您的容器没有(或可能没有)RandomAccessIterator,仍然有一种方法可以解决这个问题:
int cnt = 0;
for(auto it=container.begin(); it != container.end() && cnt < N ; ++it,++cnt) {
//
}
对我来说,它非常易读 :-). 而且不论容器类型,它的复杂度为O(N)。
std::for_each
。这可能涉及到对迭代器进行调整。 - Lightness Races in Orbittemplate<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
};
这段代码是将一个标量类型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()]);
}
};
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(); }
};
以下是一些帮助您轻松处理 indexT
范围的辅助函数:
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)}};
}
好的,快要完成了。
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
函数接受一个 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) );
}
现在我们有:
for (auto&& i : index_filter( indexes(0,6), arr )) {
}
现在我们有了一个大型的音乐器材。
更高级的过滤器也是可能的。
size_t filter[] = {1,3,0,18,22,2,4};
using std::begin;
for (auto&& i : index_filter_it( filter, begin(arr) ) )
将访问arr
中的1、3、0、18、22、2、4。然而,它不进行边界检查,除非arr.begin()[]
进行边界检查。
以上代码中可能存在错误,您应该考虑使用boost
。
如果在indexT
上实现-
和[]
,甚至可以级联这些范围。
std::views::take
从 Ranges 库 添加到您的 基于范围的 for 循环 中。这样,您就可以实现类似于 PiotrNycz's answer 的解决方案,但不使用 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;
}
N
可以大于向量的大小。这意味着,对于上面的例子,安全地使用 N = 13
,完整的向量将被打印出来。
这个解决方案不会超过end()
,对于std::list
的复杂度为O(N)
(不使用std::distance
),适用于std::for_each
,并且仅需要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 */ });
InputIterator
(例如std::istream_iterator
)一起使用 - 对此,您需要使用外部计数器。首先,我们编写一个在给定索引处停止的迭代器:
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_;
}
};
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)
);
}
使用方法:
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;
}
equal
方法让我感到困扰。我理解为什么要使用||
,但是我可以想到循环迭代器的问题(例如)。我建议只在那里引用index_
,而不必担心迭代器。另外(小事一桩),不要使用int
作为index_
,最好使用像size_t
这样的类型,因为例如int
可能只有16位。 - Matthieu M.|| this->it_ == other.it_
似乎不是正确的解决方案,因为它会破坏循环迭代器(是的,C++中的迭代器对概念使事情变得更加困难,一个单独的对象会太容易)。我想知道 Boost 适配器中的 sliced
是否处理循环迭代器。 - Matthieu M. container | std::views::take(N)
for (auto const& i: arr | std::views::take(N)) {
std::cout << i << '\n';
}
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);
c.size() < N ? c.size() : N
,意思是如果集合c的大小小于N,则返回集合c的大小,否则返回N。 - Borgleaderfor (auto&& e: input | ranges::view::take(N)) { /* stuff */ }
。 - T.C.