是否存在考虑所有元素的 ranges::views::group_by 对应物,而不是仅仅考虑连续的元素?
Does there exist a ranges::views::group_by counterpart that takes into account all elements, as opposed to just contiguous ones?
在 C++20
的 std::ranges
中,我们可以期待得到 views::group_by
1. This can be very handy, but I found a problem while playing with it. From Eric Niebler's manual 我们可以读到它“本质上,views::group_by
组 连续 元素连同二元谓词。”。让我们检查一个例子。我有一些 int
的 std::vector
,我想将它 group 分成两个范围 - 代表偶数和奇数。我最初的方法是简单地做:
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
配对在一起,因为它们 都是 和 相邻的 。类似地(除了 even)12
和 10
。但是 7
、5
和 1
将创建一个单独的组 - 它们不会与 3
和 9
分组,这不是我想要的或期待。
我们当然可以做的是 partition
ints
向量对元素进行排序,以便偶数和奇数形成两组。问题是......范围内没有 views::
partition
。这让我有两个选择,但它们都不特别吸引我:
1。 std</s>ranges::partition
在查看矢量之前:
通话中:
ranges::partition(ints, [](auto elem) { return elem % 2 == 0; });
就在我们基于范围的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';
}
}
请注意,|=
运算符周围的括号是必需的,否则将首先评估范围的组合运算符 (|
),我们将以上面的代码打印 sorted向量的元素,完全忽略分组 (???).
这种方法 okaaaay,但仍然不是很好。我更愿意有一个 group_by
可以,例如,取一个值和 return 一个键(Java
和 C#
的处理方法分组)或以任何方式考虑整个范围,或至少有 actions::partition
可用。
旁注:我看到了 views::grouping_by
仅使用连续元素的基本原理。这是最有效的方式 - 无需存储任何东西,无需返回或进一步查看。没关系,有时它是 工作的最佳工具。但我认为,对于过去使用过类似 API 的人来说,这违反直觉会造成混淆。
最后再重复一下这个问题 - 根据我提出的示例和所需方法,是否有更简洁的方法来做我想做的事情?
1 我在 cppreference 上找不到它,但我想我在某处看到了确认信息。如果我弄错了,请纠正我。
group_by 在那些其他语言中没有得到两个元素的比较器,而是从元素到相关值的元组的投影,在其上散列和比较作品。此外,他们可以自由分配额外的内存来完成工作。
在这里,你不用为你不用的东西付费 不可否认,这会稍微降低你的舒适度。如果需要,您必须明确地执行该步骤,而不是语言强迫您执行它,即使它只是无用的工作。
所以您需要 SQL 意义上的 'group by' 运算符,对吗?
就像排序操作一样,SQL 意义上的 'group by' 运算符是离线运算符,它无法在没有看到最后一个输入元素的情况下发出第一个输出元素。它的离线性质使其本质上不可组合。
目前,cpp 范围通过排序操作和分组视图支持该行为,这是在 SQL 意义上实现 'group by' 操作的一种方法。
当然,您可以通过自定义操作开发另一种实现,就像排序一样(但不同),并将其与 views::group_by 组合。
例如。 std::partition 是一种方法,或者您可以开发另一种更好的基于哈希的拆分方法,将输入范围分组到一系列桶中,每个桶中的元素共享相同的哈希值(由传入拆分的 lambda 确定方法)。
在 C++20
的 std::ranges
中,我们可以期待得到 views::group_by
1. This can be very handy, but I found a problem while playing with it. From Eric Niebler's manual 我们可以读到它“本质上,views::group_by
组 连续 元素连同二元谓词。”。让我们检查一个例子。我有一些 int
的 std::vector
,我想将它 group 分成两个范围 - 代表偶数和奇数。我最初的方法是简单地做:
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
配对在一起,因为它们 都是 和 相邻的 。类似地(除了 even)12
和 10
。但是 7
、5
和 1
将创建一个单独的组 - 它们不会与 3
和 9
分组,这不是我想要的或期待。
我们当然可以做的是 partition
ints
向量对元素进行排序,以便偶数和奇数形成两组。问题是......范围内没有 views::
partition
。这让我有两个选择,但它们都不特别吸引我:
1。 std</s>ranges::partition
在查看矢量之前:
std</s>ranges::partition
在查看矢量之前:通话中:
ranges::partition(ints, [](auto elem) { return elem % 2 == 0; });
就在我们基于范围的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';
}
}
请注意,|=
运算符周围的括号是必需的,否则将首先评估范围的组合运算符 (|
),我们将以上面的代码打印 sorted向量的元素,完全忽略分组 (???).
这种方法 okaaaay,但仍然不是很好。我更愿意有一个 group_by
可以,例如,取一个值和 return 一个键(Java
和 C#
的处理方法分组)或以任何方式考虑整个范围,或至少有 actions::partition
可用。
旁注:我看到了 views::grouping_by
仅使用连续元素的基本原理。这是最有效的方式 - 无需存储任何东西,无需返回或进一步查看。没关系,有时它是 工作的最佳工具。但我认为,对于过去使用过类似 API 的人来说,这违反直觉会造成混淆。
最后再重复一下这个问题 - 根据我提出的示例和所需方法,是否有更简洁的方法来做我想做的事情?
1 我在 cppreference 上找不到它,但我想我在某处看到了确认信息。如果我弄错了,请纠正我。
group_by 在那些其他语言中没有得到两个元素的比较器,而是从元素到相关值的元组的投影,在其上散列和比较作品。此外,他们可以自由分配额外的内存来完成工作。
在这里,你不用为你不用的东西付费 不可否认,这会稍微降低你的舒适度。如果需要,您必须明确地执行该步骤,而不是语言强迫您执行它,即使它只是无用的工作。
所以您需要 SQL 意义上的 'group by' 运算符,对吗? 就像排序操作一样,SQL 意义上的 'group by' 运算符是离线运算符,它无法在没有看到最后一个输入元素的情况下发出第一个输出元素。它的离线性质使其本质上不可组合。
目前,cpp 范围通过排序操作和分组视图支持该行为,这是在 SQL 意义上实现 'group by' 操作的一种方法。
当然,您可以通过自定义操作开发另一种实现,就像排序一样(但不同),并将其与 views::group_by 组合。
例如。 std::partition 是一种方法,或者您可以开发另一种更好的基于哈希的拆分方法,将输入范围分组到一系列桶中,每个桶中的元素共享相同的哈希值(由传入拆分的 lambda 确定方法)。