ranges::views::group_by-like function applying predicate to consecutive elements?

393 Views Asked by At

In the following small example I was trying to group elements by the difference between consecutive elements being 1. As the output shows, however, group_by's predicate is evaluated between the current element and the first element of the group being processed.

#include <iostream>
#include <range/v3/view/group_by.hpp>

int main() {
    using ranges::views::group_by;
    std::vector<int> v{1,2,3,6,7,8,9,12,14,15};
    for (auto i : v | group_by([](auto x2,auto x1){ return x2 - x1 == 1; })) {
        std::cout << i << '\n';
    }
}

Does Range-v3 offer a way to evaluate the predicate between consecutive elements of the group?

I once asked the same question for Haskell, and it turned out a namesake function is on Hackage.

2

There are 2 best solutions below

0
Roman On BEST ANSWER

group_by has been deprecated in favor of chunk_by, which does exactly what you want:

Given a source range and a binary predicate, return a range of ranges where each range contains contiguous elements from the source range such that the following condition holds: for each element in the range apart from the first, when that element and the previous element are passed to the binary predicate, the result is true. In essence, views::chunk_by groups contiguous elements together with a binary predicate.

using ranges::views::chunk_by;
std::vector<int> v{1,2,3,6,7,8,9,12,14,15};
for (auto i : v | chunk_by([](auto l, auto r){ return r - l == 1; })) {
    std::cout << i << '\n';
}

Prints

[1,2,3]
[6,7,8,9]
[12]
[14,15]
0
Enlico On

This is the ugly result of my attempt to compose what's already in Range-v3 to get what I wanted.

#include <iostream>
#include <range/v3/view/group_by.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/concat.hpp>
#include <range/v3/view/zip_with.hpp>

constexpr auto elem_and_distance_from_previous
    = [](int x, int y){ return std::make_pair(x,x - y); };
constexpr auto second_is_1 = [](auto,auto x){ return x.second == 1; };
constexpr auto get_first = [](auto x){ return x.first; };

using namespace ranges::views;
int main() {
    std::vector<int> v{1,2,3,6,7,8,9,12,14,15};
    auto w = zip_with(elem_and_distance_from_previous,v,concat(iota(0,1),v))
           | group_by(second_is_1)
           | transform([](auto x){ return x | transform(get_first); });
    std::cout << w << '\n';
}
// outputs [[1,2,3],[6,7,8,9],[12],[14,15]]

Probably as @einpoklum suggested in a comment, a copy-paste-edit of the original group_by would be much better. However I asked the question because I wanted to know if there was a way already in Range-v3 to do what I meant.