仅在基于范围的循环中对奇数(偶数)元素进行迭代

时间:2019-02-24 09:31:07

标签: c++ c++11

假设我们有一个普通数组(或其他支持基于范围的循环的容器):

const int N = 8;
int arr[N] = {0, 1, 2, 3, 4, 5, 6, 7};

使用索引或迭代器,我们可以遍历奇数元素,使索引增加两个:

for (int i = 0; i < N; i+=2)
{
   std::cout << arr[i] << std::endl;
}

如何使用基于范围的循环并避免显式的迭代器/索引或迭代跳过用法来获得相似的结果?像这样:

for (const auto& v: odd_only(arr))
{
   std::cout << v << std::endl;
}

您看到什么简单而优雅的解决方案?标准库是否包含类似内容?

4 个答案:

答案 0 :(得分:6)

不支持您的请求-但您可以编写自己的even_onlyodd_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++中处于中间状态)。

定义比较迭代器:始终为operator==operator!=,仅对于随机访问运算符,您还可以具有operator[<|>|<=|>=](→std::enable_if)。

您将找到有关如何编写迭代器here的更多详细信息–遇到std::iterator本身已被弃用时,请记住。

答案 1 :(得分:3)

关于您当前的要求;我不相信任何东西。现在,通过一个整数N对容器进行迭代,我们可以执行以下操作;我们可以编写自己的for_each类型的函数。我在下面写了一个,它就像一颗宝石!您可能还希望研究std::advance函数,因为它可能是另一种可能的实现。我在编写此功能时正在检查自己。然而;至于c数组,我不确定没有一堆额外的代码(例如类模板,包装器等),还有很多可以做的。这是我的功能。

#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 pointerfunction objectfunctorlambda,它将执行所需的操作。

在您的情况下,您试图对每个奇数或偶数索引在容器中循环2,并在循环中打印结果。在我的示例中;我以传递给此函数的lambda形式打印结果。

但是,此特定实现的唯一警告是,它将始终从索引0开始。您可以通过引入另一个integer参数(针对迭代开始的位置的偏移)来轻松扩展此内容。但是我会把它留给你做练习。

暂时,我们必须解决从C ++ 11到C ++ 17必须提供的内容。在不久的将来,随着C ++ 20的发布,我们应该具有许多强大的新功能。

答案 2 :(得分:2)

Range-v3中有针对此问题的现成解决方案。我认为,如果您不想编写自己的实现或需要更大的灵活性(例如任意跨度),这会很有用

#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注释复制)

答案 3 :(得分:2)

这并不是问题的真正答案,但是,出于价值的考虑,每当我遇到范围限制时,我都会寻找标准算法解决方案。喜欢...

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