C++ 如果设计失败,std::remove和std::remove_是否稳定?

C++ 如果设计失败,std::remove和std::remove_是否稳定?,c++,stl,complexity-theory,C++,Stl,Complexity Theory,最近(从一个SO评论中)我了解到std::remove和std:remove\u(如果稳定)。我是否错误地认为这是一个糟糕的设计选择,因为它阻止了某些优化 想象一下删除1Mstd::vector的第一个和第五个元素。由于稳定性,我们无法使用swap实现remove。相反,我们必须转移所有剩余的元素:( 如果我们不受稳定性的限制,我们可以(对于RA和BD iter)实际上有两个iter,一个在前面,第二个在后面,然后使用swap将要移除的项目带到最后。我相信聪明人可能会做得更好。我的问题是一般性的

最近(从一个SO评论中)我了解到
std::remove
std:remove\u(如果
稳定)。我是否错误地认为这是一个糟糕的设计选择,因为它阻止了某些优化

想象一下删除1M
std::vector的第一个和第五个元素。由于稳定性,我们无法使用swap实现
remove
。相反,我们必须转移所有剩余的元素:(

如果我们不受稳定性的限制,我们可以(对于RA和BD iter)实际上有两个iter,一个在前面,第二个在后面,然后使用swap将要移除的项目带到最后。我相信聪明人可能会做得更好。我的问题是一般性的,不是我所说的具体优化

<> > >编辑:请注意C++广告的“零开销”原则,还有 STD::排序< /COD>和<代码> STD::Stable排序> < /C>排序算法>

EDIT2: 优化如下所示:

对于
如果
,请删除\u:

  • bad_iter从一开始就查找谓词返回true的元素
  • good_iter从末尾查找谓词返回false的元素

当双方都找到了期望的内容时,他们交换了各自的元素。终止是在
good\u iter我假设您询问的是
stable\u remove
的假设定义,即
remove
当前是什么,以及
remove
将要实现,但实现者认为最好在任何情况下给出正确的值顺序。期望实现者能够在做与
stable\u remove
完全相同的事情上有所改进

在实践中,库无法轻松进行此优化。这取决于数据,但在决定如何删除每个元素之前,您不想花费太长时间计算将删除多少元素。例如,您可以额外进行一次计算,但在很多情况下,额外进行的计算效率低下。因为n在某些情况下,不稳定移除比稳定移除快并不一定意味着在两者之间选择自适应算法是一个好的选择

我认为
remove
sort
之间的区别在于,众所周知,排序是一个复杂的问题,有很多不同的解决方案、权衡和调整。所有这些都“简单”排序算法平均速度较慢。大多数标准算法都非常简单,
remove
是其中之一,但
sort
不是。因此,我认为将
stable\u remove
remove
定义为单独的标准函数没有多大意义

编辑:使用my tweak进行编辑(类似于
std::partition
,但无需保留右侧的值)对我来说似乎很合理。它需要一个双向迭代器,但在标准中有先例表明算法在不同的迭代器类别上表现不同,例如
std::distance
。因此,标准可以定义
unstable\u remove
,它只需要一个前向迭代器,但您需要吗如果它有一个bidi迭代器,那么它就是一个问题。标准可能不会给出算法,但它可以有这样一个短语:“如果迭代器是双向的,那么它最多只能移动
min(k,n-k)
其中
k
是删除的元素数”但请注意,如果
移动了多少步,标准目前没有说明移动的次数,因此我认为将其固定下来并不是优先事项

当然,没有什么可以阻止您实现自己的
unstable\u remove

如果我们接受标准不需要指定一个不稳定的remove,那么问题就归结到它所定义的函数是否应该被称为
stable\u remove
,预测未来的
remove
对于bidi迭代器的行为会有所不同,如果某些人聪明的话,对于前向迭代器的行为可能会有所不同对于不稳定删除的启发式方法,人们已经知道它值得一个标准函数使用。我想说的不是:如果标准函数的名称不完全规则,这不是一场灾难。从STL的
remove\u if
中删除稳定性保证可能会造成相当大的破坏。然后问题就变成,“为什么STL不把它称为
稳定_remove_if
”,对此我只能回答,除了所有答案中提出的所有要点外,STL设计过程比标准化过程快得多

stable\u-remove
也会打开一个关于理论上可能有不稳定版本的其他标准函数的蠕虫罐头。对于一个特别愚蠢的例子,
copy
应该被称为
stable\u-copy
,以防存在某种实现,在这种实现上,它明显更快地反转元素的顺序,而pying?是否应将
copy
称为
copy\u forward
,以便实现可以选择
copy\u backward
copy\u forward
中的哪一个被称为
copy
,根据哪个更快?委员会的部分工作是在某处划一条线

我认为,现实地说,当前的标准是合理的,分别定义一个
稳定的移除
和一个
移除
和一些其他约束
,是合理的,但是
以某种未指定的方式移除
只是没有提供与
以某种未指定的方式排序
相同的优化机会RoRoT是在1997发明的,就像C++正在标准化一样,但我不认为围绕代码> >删除<代码>的研究工作是完全正确的,而且是围绕着代码>排序> <代码>。我可能错了,优化<代码>删除<代码>可能是下一个大事件,如果是这样的话。
#include <vector>
#include <string>
#include <iostream>
#include <map>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <memory>
using namespace std;
int main()
{  
    vector<string> vsp;
    int n;
    cin >> n;
    for (int i =0; i < n; ++i)
    {   string s = "123456";
        s.push_back('a' + (rand() %26));
        vsp.push_back(s);
    }
    auto vsp2 = vsp;
    auto remove_start = std::chrono::high_resolution_clock::now();
    auto it=remove_if(begin(vsp),end(vsp), [](const string& s){ return s < "123456b";});
    vsp.erase(it,vsp.end());
    cout << vsp.size() << endl;
    auto remove_end = std::chrono::high_resolution_clock::now();
    cout << "erase-remove: " << chrono::duration_cast<std::chrono::milliseconds>(remove_end-remove_start).count() << " milliseconds\n";

    auto partition_start = std::chrono::high_resolution_clock::now();
    auto it2=partition(begin(vsp2),end(vsp2), [](const string& s){ return s >= "123456b";});
    vsp2.erase(it2,vsp2.end());
    cout << vsp2.size() << endl;
    auto partition_end = std::chrono::high_resolution_clock::now();
    cout << "partition-remove: " << chrono::duration_cast<std::chrono::milliseconds>(partition_end-partition_start).count() << " milliseconds\n";
}



C:\STL\MinGW>g++ test_int.cpp -O2 && a.exe
12345678
11870995
erase-remove: 1426 milliseconds
11870995
partition-remove: 658 milliseconds