在C++20
的std::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]
偶数和奇数并不都是分组的-这是因为它们并不都是连续的。3
和9
配对在一起,因为它们 * 都是好的和连续的 。类似地(除了 * 偶数 )12
和10
。但是7
,5
和1
将创建一个单独的组-它们不会与3
和9
分组,这不是我想要或期望的。
我们当然可以做的是partition
ints
向量来排列元素,这样偶数和奇数就形成了两组。问题是......在范围中没有views::
**partition
。这给我留下了两个选择,其中两个都没有特别吸引我:
1. std
ranges::partition
查看矢量前:
来电:
ranges::partition(ints, [](auto elem) { return elem % 2 == 0; });
就在我们基于 range 的for
循环之前,我们有了我们想要的输出:
[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
,例如,可以接受一个值并返回一个键(Java
和C#
处理分组的方法),或者无论如何考虑整个范围,或者至少有actions::partition
可用。
旁注:我明白了views::grouping_by
只处理连续元素的基本原理。这是最有效的方式-不需要存储任何东西,不需要返回或进一步查看。这没什么,有时它是最好的工具。但我相信它会造成混乱,因为它违反了过去使用类似API的人的直觉。
最后再重复一遍这个问题--根据我提出的例子和期望的方法,有没有更简洁的方法来做我想做的事情?
1我在cppreference上找不到它,但我想我在某个地方看到了它的确认。如果我错了,请纠正我。
4条答案
按热度按时间lf3rwulv1#
其他语言中的 group_by 不会为两个元素获取比较器,而是从元素到相关值的元组的投影,在此基础上进行散列和比较。此外,它们可以自由分配额外的内存来完成工作。
在这里,* 你不为你不使用的东西付费 * 确实会稍微降低你的舒适度。如果你需要的话,你必须明确地做这一步,而不是语言强迫你做这一步,即使这只是无用的工作。
z6psavjg2#
所以你需要一个SQL意义上的'group by'操作符,对吗?就像排序操作一样,SQL意义上的'group by'操作符是一个离线操作符,它不能在没有看到最后一个输入元素的情况下发出第一个输出元素。它的离线性质使它在内部无法组合。
目前cpp范围通过排序操作和group-by视图支持该行为,它是实现SQL意义上的“group by”操作的一种方法。
当然,您可以通过一个定制操作开发另一个实现,就像sort一样(但不同),并将其与视图::group_by组合。
例如,std::partition是一种方法,或者您可以开发另一种更好的基于散列的拆分方法,将输入范围分组到一系列桶中,每个桶中的元素共享相同的散列值(由传入拆分方法的lambda确定)。
euoag5mw3#
我目前有同样的问题,我有一个对象的矢量。他们每个人都有一个成员,这表明我需要他们在哪个组。目前最好的解决方案,我发现是:
1.按成员组ID对它们进行排序
1.使用ranges::view::chunk_by分离/分块chunk_by提供的函数将简单地检查它接收到的两个对象是否具有不同的组ID
请注意,此解决方案适用于任何数量的子组,而不仅仅是上面示例中的两个。
syqv5f0l4#
你真的需要某种类似
filter
的视图,它接受invocable返回枚举并返回std::array<std::vector<T>>
。或者,有一个你可以
transform
到一个变量,并有一个类似ranges::to_vector
的函数来划分一个变量范围(即返回tuple<vector<Ts>...>
)。