Home > Enterprise >  How to create a view over a range that always picks the first element and filters the rest?
How to create a view over a range that always picks the first element and filters the rest?

Time:10-05

I have a collection of values:

auto v = std::vector{43, 1, 3, 2, 4, 6, 7, 8, 19, 101};

Over this collection of values I want to apply a view that follows this criteria:

  • First element should always be picked.
  • From the next elements, pick only even numbers until ...
  • ... finding an element equal or greater than 6.

This is the view I tried:

auto v = std::vector{43, 1, 3, 2, 4, 6, 7, 8, 19, 101};
auto r = v |
    std::views::take(1) |
    std::views::filter([](const int x) { return !(x & 1); }) |
    std::views::take_while([](const int x) { return x < 6; });

for (const auto &x : r)
    std::cout << x << ' ';

But the execution don't even enter the print loop because the view is empty. My guess si that all the criteria is applied at once:

  1. Pick first element (43).
  2. Is odd number.
  3. View ends.

What I was expecting:

  1. Pick first element without checking anything.
  2. From the rest of elements, filter only even numbers (2, 4, 6, 8).
  3. From filtered elements, pick numbers until a number equal to or greater than 6 appears (2, 4).
  4. 43 2 4 is printed.

How can I build a view over my collection of values that behaves as I was expecting?

CodePudding user response:

With range-v3, you can use views::concat to concatenate the first element of the range and the remaining filtered elements, for example:

auto v = std::vector{43, 1, 3, 2, 4, 6, 7, 8, 19, 101};
auto r = ranges::views::concat(
  v | ranges::views::take(1),
  v | ranges::views::drop(1)
    | ranges::views::filter([](const int x) { return !(x & 1); })
    | ranges::views::take_while([](const int x) { return x < 6; })
);

Demo

CodePudding user response:

With only standard C 20, you could do it like this:

bool first = true;
auto r = v |
    std::views::filter([first](const int x) { return first || !(x & 1); }) |
    std::views::take_while([&first](const int x) { return std::exchange(first, false) || x < 6; });

Demo

It first filters on being first element, or being even number.

It then takes while being first (and at the same time sets first to false) or less than 6.

std::exchange from <utility> is a very useful, simple function.

  • Related