是否存在考虑所有元素的 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++20std::ranges 中,我们可以期待得到 views::group_by1. This can be very handy, but I found a problem while playing with it. From Eric Niebler's manual 我们可以读到它“本质上,views::group_by连续 元素连同二元谓词。”。让我们检查一个例子。我有一些 intstd::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]

偶数和奇数并未全部分组 - 那是因为它们并非都是连续的。 39 配对在一起,因为它们 都是 相邻的 。类似地(除了 even1210。但是 751 将创建一个单独的组 - 它们不会与 39 分组,这不是我想要的或期待。

我们当然可以做的是 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 一个键(JavaC# 的处理方法分组)或以任何方式考虑整个范围,或至少有 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 确定方法)。