仅在基于范围的循环中迭代奇数(偶数)元素
Iterating over odd (even) elements only in a range-based loop
假设我们有一个普通数组(或其他支持基于范围的循环的容器):
const int N = 8;
int arr[N] = {0, 1, 2, 3, 4, 5, 6, 7};
使用索引或迭代器,我们可以遍历奇数元素并将索引递增 2:
for (int i = 0; i < N; i+=2)
{
std::cout << arr[i] << std::endl;
}
如何通过使用基于范围的循环并避免显式 iterators/indexes 和迭代跳过来获得类似的结果?像这样:
for (const auto& v: odd_only(arr))
{
std::cout << v << std::endl;
}
简单而优雅的解决方案是什么样的?标准库是否包含这样的东西?
不支持您的请求 – 但您可以编写自己的 even_only
和 odd_only
实现。
基本思想是围绕有问题的容器的普通迭代器,每次我们在外部递增一次时在内部进行两次递增:
template <typename C, bool IsOdd>
class even_odd_only
{
C& c;
public:
class iterator
{
public:
// all the definitions required for iterator!
// most if not all might simply be derived from C::iterator...
// copy/move constructor/assignment as needed
// core of the wrapper: increment twice internally!
// just doing += 2 is dangerous, though, we might increment beyond
// the end iterator (undefined behaviour!)additionally, += 2 only
// is possible for random access iterators (so we limit usability)
void operator++() { ++b; if(b != e) ++b; }
// operator* and operator-> (both return *b), post-increment
// (defined in terms of pre-increment), etc...
// comparison: only needs to compare b iterators!
private:
C::iterator b;
C::iterator e; // needed for comparison to avoid incrementing beyond!
iterator(C::iterator b, C::iterator e) : b(b), e(e) { }
};
// const_iterator, too; possibly make a template of above
// and derive const and non-const iterators from?
even_odd_only(C& c) : c(c) { }
iterator begin()
{
using std::begin;
using std::end;
using std::empty;
auto b = begin(c);
// should be self-explanatory:
// skip first element in odd variant (if there is)
if constexpr(IsOdd) { if(!empty(c)) { ++b; } }
return iterator(b, end(c));
};
iterator end()
{
using std::end;
return iterator(end(c), end(c));
}
};
template <typename T>
using even_only = even_odd_base<T, false>;
template <typename T>
using odd_only = even_odd_base<T, true>;
照原样,它甚至可以使用非随机访问甚至非双向迭代器。但特别是对于 RA 迭代器,它的效率低于经典循环(由于 operator++
中的中间 if)。
定义比较迭代器:始终为 operator==
和 operator!=
,仅对于随机访问运算符,您还可以具有 operator[<|>|<=|>=]
(→ std::enable_if
)。
您会找到有关如何编写迭代器的更多详细信息 here – 但请记住,当您遇到 std::iterator
本身现在已被弃用时。
至于你现在问的是什么;我不相信任何东西存在。现在,对于通过某个整数 N
遍历容器,我们可以执行以下操作;我们可以编写自己的 for_each
类型的函数。我在下面写了一个,它像 gem 一样工作!您可能还想查看 std::advance
函数,因为它可能是另一种可能的实现。我在编写此函数时亲自检查了这一点。然而;至于 c 数组,如果没有一堆额外的代码,例如 class 模板、包装器等,我不确定是否可以做很多事情。这是我的功能。
#include <array>
#include <vector>
#include <iterator>
template<typename Container, typename Function>
void for_each_by_n( Container&& cont, Function f, unsigned increment_by = 1) {
if ( increment_by == 0 ) return; // must check this for no op
using std::begin;
auto it = begin(cont);
using std::end;
auto end_it = end(cont);
while( it != end_it ) {
f(*it);
for ( unsigned n = 0; n < increment_by; ++n ) {
if ( it == end_it ) return;
++it;
}
}
}
int main() {
std::array<int,8> arr{ 0,1,2,3,4,5,6,7 };
std::vector<double> vec{ 1.2, 1.5, 1.9, 2.5, 3.3, 3.7, 4.2, 4.8 };
auto l = [](auto& v) { std::cout << v << ' '; };
for_each_by_n(arr, l); std::cout << '\n';
for_each_by_n(vec, l); std::cout << '\n';
for_each_by_n(arr, l, 2); std::cout << '\n';
for_each_by_n(arr, l, 4); std::cout << '\n';
for_each_by_n(vec, l, 3); std::cout << '\n';
for_each_by_n(vec, l, 5); std::cout << '\n';
for_each_by_n(arr, l, 8); std::cout << '\n';
for_each_by_n(vec, l, 8); std::cout << '\n';
// sanity check to see if it doesn't go past end.
for_each_by_n(arr, l, 9); std::cout << '\n';
for_each_by_n(vec, l, 9); std::cout << '\n';
return 0;
}
-输出-
0 1 2 3 4 5 6 7
1.2 1.5 1.9 2.5 3.3 3.7 4.2 4.8
0 2 4 6
0 4
1.2 2.5 4.2
1.2 3.7
0
1.2
0
1.2
我喜欢上面这个例子的地方在于,你不仅可以在循环中递增某个整数 N
;上述函数还接受 function pointer
、function object
、functor
或 lambda
,它将执行所需的操作。
在您的情况下,您试图通过 2 循环遍历您的容器,以获得奇数或每个偶数索引,并在循环中打印结果。在我的例子中;我正在以传递给此函数的 lambda 形式打印结果。
然而,这个特定实现的唯一警告是它总是从索引 0 开始。您可以通过引入另一个 integer
参数来轻松扩展它,作为迭代开始位置的偏移量;但我会把它留给你作为练习。
目前我们必须满足于 C++11 到 C++17 必须提供的功能。在不久的将来,随着 C++20 的发布,我们应该会有许多新的强大功能。
这个问题在Range-v3中有现成的解决方案。如果您不想编写自己的实现或需要更大的灵活性,我认为这会很有用(f.e。任意步幅)
#include <range/v3/all.hpp>
void example()
{
int data[8] = {0, 1, 2, 3, 4, 5, 6, 7};
for (auto i : ranges::view::stride(data, 2))
{
std::cout << i << std::endl;
}
}
(复制自@hlt 评论)
这并不是问题的真正答案,但是——就其价值而言——每当我 运行 进入 ranged-for 的限制时,我都会寻找标准算法解决方案。喜欢...
#include <algorithm>
#include <iostream>
#include <iterator>
#include <utility>
int main()
{
int arr[] {0, 1, 2, 3, 4, 5, 6, 7};
std::copy_if(
std::begin(arr), std::end(arr),
std::ostream_iterator<int>(std::cout, "\n"),
[is_odd_element = true](int n) mutable {
return std::exchange(is_odd_element, not is_odd_element);
});
}
假设我们有一个普通数组(或其他支持基于范围的循环的容器):
const int N = 8;
int arr[N] = {0, 1, 2, 3, 4, 5, 6, 7};
使用索引或迭代器,我们可以遍历奇数元素并将索引递增 2:
for (int i = 0; i < N; i+=2)
{
std::cout << arr[i] << std::endl;
}
如何通过使用基于范围的循环并避免显式 iterators/indexes 和迭代跳过来获得类似的结果?像这样:
for (const auto& v: odd_only(arr))
{
std::cout << v << std::endl;
}
简单而优雅的解决方案是什么样的?标准库是否包含这样的东西?
不支持您的请求 – 但您可以编写自己的 even_only
和 odd_only
实现。
基本思想是围绕有问题的容器的普通迭代器,每次我们在外部递增一次时在内部进行两次递增:
template <typename C, bool IsOdd>
class even_odd_only
{
C& c;
public:
class iterator
{
public:
// all the definitions required for iterator!
// most if not all might simply be derived from C::iterator...
// copy/move constructor/assignment as needed
// core of the wrapper: increment twice internally!
// just doing += 2 is dangerous, though, we might increment beyond
// the end iterator (undefined behaviour!)additionally, += 2 only
// is possible for random access iterators (so we limit usability)
void operator++() { ++b; if(b != e) ++b; }
// operator* and operator-> (both return *b), post-increment
// (defined in terms of pre-increment), etc...
// comparison: only needs to compare b iterators!
private:
C::iterator b;
C::iterator e; // needed for comparison to avoid incrementing beyond!
iterator(C::iterator b, C::iterator e) : b(b), e(e) { }
};
// const_iterator, too; possibly make a template of above
// and derive const and non-const iterators from?
even_odd_only(C& c) : c(c) { }
iterator begin()
{
using std::begin;
using std::end;
using std::empty;
auto b = begin(c);
// should be self-explanatory:
// skip first element in odd variant (if there is)
if constexpr(IsOdd) { if(!empty(c)) { ++b; } }
return iterator(b, end(c));
};
iterator end()
{
using std::end;
return iterator(end(c), end(c));
}
};
template <typename T>
using even_only = even_odd_base<T, false>;
template <typename T>
using odd_only = even_odd_base<T, true>;
照原样,它甚至可以使用非随机访问甚至非双向迭代器。但特别是对于 RA 迭代器,它的效率低于经典循环(由于 operator++
中的中间 if)。
定义比较迭代器:始终为 operator==
和 operator!=
,仅对于随机访问运算符,您还可以具有 operator[<|>|<=|>=]
(→ std::enable_if
)。
您会找到有关如何编写迭代器的更多详细信息 here – 但请记住,当您遇到 std::iterator
本身现在已被弃用时。
至于你现在问的是什么;我不相信任何东西存在。现在,对于通过某个整数 N
遍历容器,我们可以执行以下操作;我们可以编写自己的 for_each
类型的函数。我在下面写了一个,它像 gem 一样工作!您可能还想查看 std::advance
函数,因为它可能是另一种可能的实现。我在编写此函数时亲自检查了这一点。然而;至于 c 数组,如果没有一堆额外的代码,例如 class 模板、包装器等,我不确定是否可以做很多事情。这是我的功能。
#include <array>
#include <vector>
#include <iterator>
template<typename Container, typename Function>
void for_each_by_n( Container&& cont, Function f, unsigned increment_by = 1) {
if ( increment_by == 0 ) return; // must check this for no op
using std::begin;
auto it = begin(cont);
using std::end;
auto end_it = end(cont);
while( it != end_it ) {
f(*it);
for ( unsigned n = 0; n < increment_by; ++n ) {
if ( it == end_it ) return;
++it;
}
}
}
int main() {
std::array<int,8> arr{ 0,1,2,3,4,5,6,7 };
std::vector<double> vec{ 1.2, 1.5, 1.9, 2.5, 3.3, 3.7, 4.2, 4.8 };
auto l = [](auto& v) { std::cout << v << ' '; };
for_each_by_n(arr, l); std::cout << '\n';
for_each_by_n(vec, l); std::cout << '\n';
for_each_by_n(arr, l, 2); std::cout << '\n';
for_each_by_n(arr, l, 4); std::cout << '\n';
for_each_by_n(vec, l, 3); std::cout << '\n';
for_each_by_n(vec, l, 5); std::cout << '\n';
for_each_by_n(arr, l, 8); std::cout << '\n';
for_each_by_n(vec, l, 8); std::cout << '\n';
// sanity check to see if it doesn't go past end.
for_each_by_n(arr, l, 9); std::cout << '\n';
for_each_by_n(vec, l, 9); std::cout << '\n';
return 0;
}
-输出-
0 1 2 3 4 5 6 7
1.2 1.5 1.9 2.5 3.3 3.7 4.2 4.8
0 2 4 6
0 4
1.2 2.5 4.2
1.2 3.7
0
1.2
0
1.2
我喜欢上面这个例子的地方在于,你不仅可以在循环中递增某个整数 N
;上述函数还接受 function pointer
、function object
、functor
或 lambda
,它将执行所需的操作。
在您的情况下,您试图通过 2 循环遍历您的容器,以获得奇数或每个偶数索引,并在循环中打印结果。在我的例子中;我正在以传递给此函数的 lambda 形式打印结果。
然而,这个特定实现的唯一警告是它总是从索引 0 开始。您可以通过引入另一个 integer
参数来轻松扩展它,作为迭代开始位置的偏移量;但我会把它留给你作为练习。
目前我们必须满足于 C++11 到 C++17 必须提供的功能。在不久的将来,随着 C++20 的发布,我们应该会有许多新的强大功能。
这个问题在Range-v3中有现成的解决方案。如果您不想编写自己的实现或需要更大的灵活性,我认为这会很有用(f.e。任意步幅)
#include <range/v3/all.hpp>
void example()
{
int data[8] = {0, 1, 2, 3, 4, 5, 6, 7};
for (auto i : ranges::view::stride(data, 2))
{
std::cout << i << std::endl;
}
}
(复制自@hlt 评论)
这并不是问题的真正答案,但是——就其价值而言——每当我 运行 进入 ranged-for 的限制时,我都会寻找标准算法解决方案。喜欢...
#include <algorithm>
#include <iostream>
#include <iterator>
#include <utility>
int main()
{
int arr[] {0, 1, 2, 3, 4, 5, 6, 7};
std::copy_if(
std::begin(arr), std::end(arr),
std::ostream_iterator<int>(std::cout, "\n"),
[is_odd_element = true](int n) mutable {
return std::exchange(is_odd_element, not is_odd_element);
});
}