Search code examples
c++range-v3

Filtered range (circular) traversal starting from iterator over original sequence


Say you have a container and have an iterator to some element of that container. The requirement is to circularly advance that iterator but with a filter. Now, creating a filtered (circular) view over the sequence is trivial but how do you initiate the traversal when all you have is an iterator over the original? I was thinking about concatenating two ranges, like this:

#include <iostream>

#include <range/v3/all.hpp>
using namespace ranges;

int main () {
    std::vector< int > xs{ -4, 1, 3, -4, 2, -3, 6, 7, 8, -9, -10 };

    auto iter = xs.begin ();
    std::advance (iter, 8);

    std::cout << *iter << std::endl;

    auto lhs = make_subrange (iter, xs.end ());
    auto rhs = make_subrange (xs.begin (), iter);

    auto is_pos = [](auto x) { return x >= 0; };
    auto rng = views::cycle (views::concat (lhs, rhs) | views::filter (is_pos));

    auto next = ++rng.begin ();
    std::cout << *next << std::endl;

    return 0;
}

Is there a simpler approach?


Solution

  • Yes, instead of concatenating 2 sub-ranges at iter, you could simply drop the elements before iter.

    So this snippet

    auto lhs = make_subrange (iter, xs.end ());
    auto rhs = make_subrange (xs.begin (), iter);
    
    auto rng = views::cycle (views::concat (lhs, rhs) | views::filter (is_pos));
    

    becomes

    auto rng = xs | views::cycle
                  | views::drop(distance(xs.begin(), iter))
                  | views::filter(is_pos);
    

    Note that while this solution is simpler, it cycles over all the unfiltered elements, and applies the filter predicate multiple times. Your solution is probably more efficient than this one.