Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slice view for C++20 ranges

Tags:

c++

slice

stl

c++20

Python's itertools has the islice(seq, start, stop, step) procedure that takes a sequence and returns an iterator of every stepth value of the sequence values between start and stop.

Does the Ranges library of C++20 provide a similar functionality, e.g. a function like slice that takes a random access iterator start, a sentinel stop, and a step value step, and that returns a random access iterator that iterates over every stepth value between start and stop?

In case it does not, can such an iterator adapter be implemented using the primitives provided by the Ranges library?

(I know how I can implement such an adapter by hand, so this is not the question.)

like image 708
Marc Avatar asked Jun 17 '19 20:06

Marc


2 Answers

Not quite.

C++20 will have view::iota which gives you a sequence from a starting value to a sentinel. However, it does not have the stride functionality. It only increments (via ++).

However, you can combine it with range-v3's view::stride to add in the steps. That is:

auto evens = view::iota(0, 100) | view::stride(2); // [0, 2, 4, 6, ... ]

For existing ranges, there's view::slice, which also doesn't take a stride. But these are orthogonal and layer nicely:

auto even_teens  = view::iota(0, 100)
                 | view::slice(10, 20)
                 | view::stride(2); // [10, 12, 14, 16, 18]
like image 61
Barry Avatar answered Oct 02 '22 05:10

Barry


Unfortunaltely, slice and stride of Range-v3, as presented in Barry'sanswer, are not (yet) available in the Ranges library of C++20. However, you can replace slice by combining std::views::drop_while and std::views::take_while. To replace stride, you can use the range adaptor std::views::filter and pass a specific lambda expression to it. To filter for every other element as in Barry's example, I would use a stateful lambda expression with an init capture. You can put everything together to represent the range [10, 12, 14, 16, 18] as follows:

auto even_teens = std::views::iota(0, 100)
                | std::views::drop_while([](int i) { return i < 10; })
                | std::views::take_while([](int i) { return i < 20; })
                | std::views::filter([s = false](auto const&) mutable { return s = !s; });

For a more universal stride solution, you can use a counter along with the modulo operator in the lambda expression. To be able to specify the stride size n in a readable way, I would use the following lambda expression, which provides another lambda expression that keeps track of the stride operation:

auto stride = [](int n) {
    return [s = -1, n](auto const&) mutable { s = (s + 1) % n; return !s; };
};

All in all, the final solution looks like this:

auto even_teens = std::views::iota(0, 100)
                | std::views::drop_while([](int i) { return i < 10; })
                | std::views::take_while([](int i) { return i < 20; })
                | std::views::filter(stride(2));

Code on Wandbox

like image 34
honk Avatar answered Oct 02 '22 03:10

honk