【问题标题】:stable_partition on forward iterators前向迭代器上的 stable_partition
【发布时间】:2020-04-25 19:38:14
【问题描述】:

1. 现行标准草案中std::stable_partitionis的规范:

template<class BidirectionalIterator, class Predicate>
BidirectionalIterator stable_partition(
    BidirectionalIterator first, BidirectionalIterator last, Predicate pred);

我没有发现BidirectionalIterator 应该是双向迭代器的要求,但顾名思义。(见下文)

2. 在 SGI STL 中,规范is

template <class ForwardIterator, class Predicate>
ForwardIterator stable_partition(
    ForwardIterator first, ForwardIterator last, Predicate pred);

类型要求:ForwardIterator是Forward Iterator的模型。

当前标准和 SGI 版本的复杂性规范是相同的:最多 N log N 交换,如果有足够的额外内存并且恰好 N 谓词和投影的应用程序,则只有 O(N) 交换。

3. libstdc++libc++ 中的声明如下:

template<typename ForwardIterator, typename Predicate>
ForwardIterator stable_partition(
    ForwardIterator first, ForwardIterator last, Predicate pred);

在 GCC 和 Clang 中,std::stable_partition 确实适用于前向迭代器。例如:

int main() {
    std::forward_list<int> list{1, 4, 5, 2, 3, 0};
    std::stable_partition(list.begin(), list.end(), [](int i) { return i < 3;});

    for (auto v : list)
        std::cout << v << ' ';
}

compiles and produces the correct output。 Microsoft 的编译器无法编译此代码(没有-- 运算符)。英特尔的成功了。

我有两个相关的问题:

  • std::stable_partition 是否至少接受标准的双向迭代器或名称 BidirectionalIterator 具有误导性?
  • 如果它确实只接受双向迭代器,为什么不再支持前向迭代器?

编辑

找到this clause:

如果算法的模板参数命名为BidirectionalIteratorBidirectionalIterator1BidirectionalIterator2,则模板参数应满足Cpp17BidirectionalIterator的要求。

所以,只剩下第二个问题了。

【问题讨论】:

  • 可能相关:stackoverflow.com/questions/21554635/… 如果是实现效率的问题,std::reverse 也仅限于双向迭代器。
  • @Bob__ 感谢您的链接。但是这里讨论的“慢”和“快”版本之间的选择与额外内存缓冲区的可用性有关,但与迭代器类别无关。
  • 当然,但该标准还要求在交换方面具有一定的复杂性。在前面关于partition 的行中,在 25.7.4.8 中它说 “如果 first 的类型满足命名空间 std 或模型 bidirectional_iterator 中的重载的 Cpp17BidirectionalIterator 要求,则最多交换 N/2用于命名空间范围内的重载,否则最多交换 N 个。”。所以也许需要一个双向迭代器来确保25.7.4.13的要求。
  • @Bob__ 支持前向迭代器的 SGI STL 与不支持前向迭代器的标准库具有相同的复杂性规范。
  • FWIW,即使在 C++98 中它已经需要 BidirectionalIterator: lirmm.fr/~ducour/Doc-objets/ISO+IEC+14882-1998.pdf

标签: c++ stl


【解决方案1】:

首先,没有放弃任何支持,std::stable_partition 按照标准一直要求BidirectionalIterator。这并不意味着不允许库的实现者对输入参数给予较少的限制(如果它继续遵守标准 ofc 的其他部分)。因此,Gcc、Clang 和 Intel 使用他们的权利并使代码更加通用。您可以将其视为标准库的编译器扩展。

说到这里可能有人会问为什么标准需要BidirectionalIterator。我想这是可能的,因为标准的作者没有看到没有这个要求就可以满足复杂性要求的方法。 gcc 的作者可能找到了一种比标准预期的更好的方法。查看 gcc 源代码有点证实了这一点。 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_algo.h#L1613

编辑:

我已经研究了 GCC 库的实现,我想我明白了。 ForwardIteratorBidirectionalIteratorRandomAccessIterator 的实现与 std::stable_partition 不同。这是由于分区算法使用的std::rotate 的不同实现。因此,对于前向迭代器,交换的数量更大并且可以超过(last - first) * log(last - first)。 看herehere

【讨论】:

  • std::stable_partition(和许多其他算法)从 Alexander Stepanov 和他的同事开发的 STL 成为标准。 GCC/libstdc++ 对 std::stable_partition 的实现几乎逐行遵循 SGI STL 的实现(更不用说相同的文件名 - stl_algo.h)。在 STL 中,std::stable_partition 接受具有相同复杂性要求的前向迭代器。鉴于 Stepanov 的 STL 是标准库的基础,因此“标准的作者没有看到符合复杂性要求的方法”几乎是不可能的。
  • 如果确实如此,它看起来应该是一个应该修复的错误。可能我根本看不出从前向迭代器到双向迭代器的转变的深层原因。因此,问题。
  • @Evg 我看了一点,我想我找到了。
  • 实现是不同的,但std::rotate 对于正向和双向迭代器都具有线性复杂性。 stable_partition 进行的交换的渐近数对于两个迭代器类别应该相同。一个常数因素可以改变,但这不是完全禁止前向迭代器的好理由。例如,partition 进行的交换次数对于正向和双向迭代器相差 2 倍,但两者都是可以接受的。
  • @Evg 请注意,标准没有提到渐近复杂性,而是确切的最大交换量,这将是无效的,但仍然是线性的
【解决方案2】:

这个问题似乎有历史原因而不是数学原因。翻阅 Alexander Stepanov 的papers,我发现了这个:“Partition and Related Functions”。

它包含以下段落:

备注: 有趣的是,这种出色的算法不在需要双向迭代器进行分区的 C++ 标准中。自从八十年代中期我在 CACM 的 Bentley 专栏中第一次读到它以来,我已经知道、实施并教授了这个算法很长一段时间了。但不知何故,我最初的 STL 提案确实为partitionstable_partition 指定了双向迭代器。它们都在 SGI STL 中得到了纠正,但大多数供应商仍然落后。这件小事已经困扰我十多年了;最麻烦的部分是遗漏的事实。它怎么发生的?我怀疑这个解释很简单:虽然在 90 年代初期,我已经理解了将每个算法都降低到最低要求的想法,而且我也知道,当我们对数据了解得更多时,可以使用更好的算法来实现相同的操作。它们被应用时,我还没有完全意识到需要为最弱的情况提供一种算法,如果这样的算法可用的话。花了几年的时间才理解“填充算法空间”的重要性。

以下简单算法

template <typename I, // I models Forward Iterator
          typename N, // N models Integer
          typename P> // P models Unary Predicate
pair<I, I> stable_partition_inplace_n(I f, N n, P p)
{
    if (n == 0) return make_pair(f, f);
    if (n == 1) {
        I l = successor(f);
        if (p(*f)) l = f;
        return make_pair(f, l);
    }
    pair<I, I> i = stable_partition_inplace_n(f, n/2, p);
    pair<I, I> j = stable_partition_inplace_n(i.second, n – n/2, p);
    return make_pair(rotate(i.first, i.second, j.first), j.second);
}

在那篇论文中给出。它与前向迭代器一起工作,并在最坏的情况下执行O(N log N) 交换。

【讨论】:

    猜你喜欢
    • 2017-09-02
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    • 2015-02-27
    • 2012-02-03
    • 2020-01-21
    • 2012-10-27
    • 2014-01-20
    相关资源
    最近更新 更多