2

C++20'sstd::ranges中,我们可以预期得到1。这可能非常方便,但我在玩它时发现了一个问题。从 Eric Niebler 的手册中,我们可以读到“本质上,将连续元素与二元谓词组合在一起。。让我们看一个例子。我有一些s ,我想将分成两个范围 - 代表偶数和奇数。我最初的方法是简单地做:views::group_byviews::group_bystd::vectorint

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配对在一起,因为它们都是oodcontiguous。类似地(除了是even1210。但是7, 5and1将创建一个单独的组 - 它们不会与3and分组9,这不是我想要或期望的。

我们当然可以做的是对partition向量ints进行排序,以便偶数和赔率形成两组。问题是......范围内没有。这给我留下了两种选择,它们都没有特别吸引我:views::partition

1.查看矢量前:stdranges::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';
    }
}

请注意,|=运算符周围的括号是必需的,因为否则|将首先评估范围的组合运算符 ( ),我们最终将使用上面的代码打印向量的排序元素,完全忽略分组 ( ??? )。

这种方法还可以,但仍然不是很好。我更喜欢有一个group_by可以,例如,取一个值并返回一个键(Java's 和C#' 处理分组的方法),或者无论如何考虑整个范围,或者至少有actions::partition可用的。

旁注:我看到了views::grouping_by仅使用连续元素的基本原理。这是最有效的方式——无需存储任何东西,无需返回或进一步查看。没关系,有时它是工作最佳工具。但我认为,对于过去使用过类似 API 的人来说,这违反直觉,会造成混乱。

最后重复这个问题 - 根据我提出的示例和所需的方法,有没有更简洁的方法来做我想做的事?


1我在cppreference上找不到它,但我想我在某处看到了它所在的确认信息。如果我弄错了,请纠正我。

4

2 回答 2

0

其他语言中的group_by没有得到两个元素的比较器,而是从元素到相关值的元组的投影,散列和比较在该元组上起作用。此外,他们可以自由分配额外的内存来完成工作。

在这里,您不必为不使用的东西付费,这确实会稍微降低您的舒适度。如果您需要它,您必须明确地执行该步骤,而不是语言强加给您,即使它只是无用的制作工作。

于 2019-10-31T15:23:27.790 回答
0

因此,您需要 SQL 意义上的“分组依据”运算符,对吧?就像排序操作一样,SQL 意义上的 'group by' 运算符是离线运算符,它不能在没有看到最后一个输入元素的情况下发出第一个输出元素。它的离线特性使其本质上是不可组合的。

目前,cpp 范围通过排序操作和分组视图支持该行为,它是在 SQL 意义上实现“分组”操作的一种方法。

当然,您可以通过自定义操作开发另一个实现,就像排序(但不同),并使用views::group_by 组合它。

例如。std::partition 是一种方法,或者您可以开发另一种更好的基于哈希的拆分方法,将输入范围分组到一个桶范围内,每个桶中的元素共享相同的哈希值(由传递给拆分方法的 lambda 确定)。

于 2021-04-21T13:56:25.770 回答