0

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.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    I'm speculating that this doesn't exist. But - can't you just write your own? Also, when not all elements need to meet the same criterion, I wouldn't call it `group_by` but rather something else. – einpoklum Jan 31 '21 at 09:55
  • @einpoklum, I hope it's clearer now. In my intention all elements in each group must be the previous element in the group + 1. Well, the first element on the whole vector has no previous element... – Enlico Jan 31 '21 at 10:01
  • 1
    Actually, the question is clear enough, I was just making a suggestion. It shouldn't be too difficult to take the source of group_by, [from here](https://github.com/ericniebler/range-v3/blob/master/include/range/v3/view/group_by.hpp), and change the `Pred` structure to do what you want. – einpoklum Jan 31 '21 at 10:02
  • It's a different algorithm that you're describing as opposed to what is conventionally considered "group by". I've seen it called "group with" in some functional programming libraries – Aluan Haddad Jan 31 '21 at 12:08
  • @AluanHaddad, I guess if the current `group_by` was called `group_with` then a good name for the different algorithm I'm describing would probably be `group_by`. In the end, they would just be two different names for "slightly" different algorithms. As regards other FP languages, as per the link in my question, Haskell has [`Data.List.groupBy`](https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-List.html#v:groupBy) (which works like `ranges::views::group_by`) and [`Data.List.GroupBy.groupBy`](https://hackage.haskell.org/package/groupBy). – Enlico Jan 31 '21 at 12:33
  • @einpoklum, yeah, probably it's easy. I just didn't expect such a "variant" of `group_by` is not in Range-v3 (given all the stuff that it's in there). – Enlico Jan 31 '21 at 13:57
  • I Just thought you might find the designs of such apis useful in crafting your own implementation whatever you choose to call it. Ramda JavaScript is a good example. – Aluan Haddad Jan 31 '21 at 14:39

2 Answers2

1

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]
Roman
  • 70
  • 1
  • 6
0

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.

Enlico
  • 23,259
  • 6
  • 48
  • 102