c++ 是否存在一个ranges::views::group_by对等体来考虑所有元素,而不是仅仅考虑相邻的元素?

6xfqseft  于 2023-04-01  发布在  其他
关注(0)|答案(4)|浏览(173)

C++20std::ranges中,我们可以期望得到views::group_by 1。这可能非常方便,但我在玩它的时候发现了一个问题。从Eric Niebler的manual中我们可以读到它“* 本质上,views::group_by连续的元素与二元 predicate 组合在一起。让我们检查一个示例。我有一个std::vector,其中包含一些int s,我想 *将 * 分组为两个范围-代表偶数和奇数。我最初的方法是简单地执行:

int main() {
    using namespace ranges;

    std::vector<int> ints = {3, 9, 12, 10, 7, 5, 1, 4, 8};

    for (auto rng : ints | views::group_by(
            [](auto lhs, auto rhs) {
                const bool leftEven = lhs % 2 == 0;
                const bool rightEven = rhs % 2 == 0;

                return (leftEven && rightEven) || (!leftEven && !rightEven);
            })) {
        std::cout << rng << '\n';
    }
}

但这行不通。或者换句话说,它可以工作,但对于熟悉其他语言(甚至API)中类似操作的人来说,会产生意想不到的结果(我想对于一些人来说)。这个程序的输出是:

[3,9]
[12,10]
[7,5,1]
[4,8]

偶数和奇数并不都是分组的-这是因为它们并不都是连续的。39配对在一起,因为它们 * 都是好的连续的 。类似地(除了 * 偶数 1210。但是751将创建一个单独的组-它们不会与39分组,这不是我想要或期望的。
我们当然可以做的是partitionints向量来排列元素,这样偶数和奇数就形成了两组。问题是......在范围中没有
views::**partition。这给我留下了两个选择,其中两个都没有特别吸引我:

1. stdranges::partition查看矢量前:

来电:

ranges::partition(ints, [](auto elem) { return elem % 2 == 0; });

就在我们基于 rangefor循环之前,我们有了我们想要的输出:

[8,4,12,10]
[7,5,1,9,3]

我不喜欢它,因为它缺乏可组合性--这是ranges的关键因素之一。老实说,我也不想对向量进行分区。我想把它的元素分成两组--偶数和奇数。

2.使用actions::sort,使用奇偶比较器对向量进行排序:

int main() {
    using namespace ranges;

    std::vector<int> ints = {3, 9, 12, 10, 7, 5, 1, 4, 8};

    auto evens_first = [](auto lhs, auto rhs) { return lhs % 2 == 0 && rhs % 2 != 0; };

    for (auto rng : (ints |= actions::sort(evens_first)) | views::group_by(
            [](auto lhs, auto rhs) {
                const bool leftEven = lhs % 2 == 0;
                const bool rightEven = rhs % 2 == 0;

                return (leftEven && rightEven) || (!leftEven && !rightEven);
            })) {
        std::cout << rng << '\n';
    }
}

请注意,|=运算符周围的括号是必需的,因为否则将首先计算范围的组合运算符(|),并且我们将以上面的代码打印向量的排序元素结束,完全忽略分组(???)。
这种方法是 okaaaay,但仍然不是很好。我更希望有一个group_by,例如,可以接受一个值并返回一个键(JavaC#处理分组的方法),或者无论如何考虑整个范围,或者至少有actions::partition可用。
旁注:我明白了views::grouping_by只处理连续元素的基本原理。这是最有效的方式-不需要存储任何东西,不需要返回或进一步查看。这没什么,有时它是最好的工具。但我相信它会造成混乱,因为它违反了过去使用类似API的人的直觉。
最后再重复一遍这个问题--根据我提出的例子和期望的方法,有没有更简洁的方法来做我想做的事情?
1我在cppreference上找不到它,但我想我在某个地方看到了它的确认。如果我错了,请纠正我。

lf3rwulv

lf3rwulv1#

其他语言中的 group_by 不会为两个元素获取比较器,而是从元素到相关值的元组的投影,在此基础上进行散列和比较。此外,它们可以自由分配额外的内存来完成工作。
在这里,* 你不为你不使用的东西付费 * 确实会稍微降低你的舒适度。如果你需要的话,你必须明确地做这一步,而不是语言强迫你做这一步,即使这只是无用的工作。

z6psavjg

z6psavjg2#

所以你需要一个SQL意义上的'group by'操作符,对吗?就像排序操作一样,SQL意义上的'group by'操作符是一个离线操作符,它不能在没有看到最后一个输入元素的情况下发出第一个输出元素。它的离线性质使它在内部无法组合。
目前cpp范围通过排序操作和group-by视图支持该行为,它是实现SQL意义上的“group by”操作的一种方法。
当然,您可以通过一个定制操作开发另一个实现,就像sort一样(但不同),并将其与视图::group_by组合。
例如,std::partition是一种方法,或者您可以开发另一种更好的基于散列的拆分方法,将输入范围分组到一系列桶中,每个桶中的元素共享相同的散列值(由传入拆分方法的lambda确定)。

euoag5mw

euoag5mw3#

我目前有同样的问题,我有一个对象的矢量。他们每个人都有一个成员,这表明我需要他们在哪个组。目前最好的解决方案,我发现是:
1.按成员组ID对它们进行排序
1.使用ranges::view::chunk_by分离/分块chunk_by提供的函数将简单地检查它接收到的两个对象是否具有不同的组ID
请注意,此解决方案适用于任何数量的子组,而不仅仅是上面示例中的两个。

syqv5f0l

syqv5f0l4#

你真的需要某种类似filter的视图,它接受invocable返回枚举并返回std::array<std::vector<T>>
或者,有一个你可以transform到一个变量,并有一个类似ranges::to_vector的函数来划分一个变量范围(即返回tuple<vector<Ts>...>)。

相关问题