Iterating over an odd (even) elements only in a range-based loop

前端 未结 4 1279
无人共我
无人共我 2021-02-15 16:23

Suppose we have a plain array (or other container which support range based loops):

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

Usi

相关标签:
4条回答
  • 2021-02-15 17:00

    This isn't really an answer to the question, but—for what it is worth—whenever I run into a limitation of ranged-for, I look for a standard algorithm solution. Like...

    #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);
            });
    }
    
    0 讨论(0)
  • 2021-02-15 17:11

    As for what you are currently asking; I do not believe anything exists yet. Now as for iterating over a container by some integer N we can do the following; we can write our own for_each type of function. I've written one below and it works like a gem! You may also want to look into the std::advance function as well for it can be another possible implementation. I was checking that out myself as I was writing this function. However; as for c arrays I'm not sure there is much one can do without a bunch of extra code such as class templates, wrappers, etc. Here is my function.

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

    -Output-

     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
    

    What I like about this example above is that not only can you increment through a loop by some integer N; the above function also takes a function pointer, function object, functor, or lambda and it will perform the required action.

    In your case you was trying to loop through your container by 2 for ever odd or every even index and within the loop you were printing the results. Here in my example; I'm printing the results in the form of a lambda that is being passed to this function.

    However the only caveat with this particular implementation is that it will always start from index 0. You could easily expand on this by introducing another integer parameter as to an offset of where the iteration will begin; but I'll leave that up to you to do as an exercise.

    For the time being we have to settle for what C++11 through C++17 has to offer. In the near future we should have many new and powerful features with the release of C++20.

    0 讨论(0)
  • 2021-02-15 17:16

    There is a ready-made solution for this problem in the Range-v3. I think this can be useful if you don’t want to write your own implementation or need more flexibility (f.e. arbitrary stride)

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

    (copied from @hlt comment)

    0 讨论(0)
  • 2021-02-15 17:18

    There's no support for what you request – but you might write your own even_only and odd_only implementations.

    Basic idea is to wrap around the normal iterator of the container in question and do a double increment internally each time we increment once externally:

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

    As is, it would work even with non-random-access and even non-bidirectional iterators. But especially for RA-iterators, it's less efficient than the classic loop (due to the intermediate if in operator++).

    Defining comparison iterators: always operator== and operator!=, only for random access operators you can additionally have operator[<|>|<=|>=] (→ std::enable_if).

    You'll find more details about how to write an iterator here – keep in mind when you encounter, though, that std::iterator itself is deprecated now.

    0 讨论(0)
提交回复
热议问题