14

最近(1つのSOコメントから)私はそれを学び、std::remove安定std:remove_ifしています。特定の最適化を妨げるので、これがひどい設計選択であると考えるのは間違っていますか?

1M の 1 番目と 5 番目の要素を削除することを想像してくださいstd::vectorremove安定性のため、スワップで実装することはできません。代わりに、残りのすべての要素をシフトする必要があります。:(

安定性によって制限されていなければ、(RA と BD iter の場合) 実質的に 2 つの iter (前から 1 つ、後ろから 2 つ) を使用し、スワップを使用して削除するアイテムを終了させることができます。頭のいい人ならもっとうまくやれると思います。私の質問は一般的なものであり、私が話している特定の最適化についてではありません。

編集: C++ はゼロ オーバーヘッドの原則をアドバタイズすることに注意してください。また、ソート アルゴリズムもありstd::sortますstd::stable_sort

EDIT2: 最適化は次のようになります。

の場合remove_if:

  • bad_iter は、述語が true を返す要素を最初から探します。
  • good_iter は、述語が false を返す要素を最後から探します。

両方が期待されるものを見つけたら、要素を交換します。で終了ですgood_iter <= bad_iter

役立つ場合は、クイック ソート アルゴリズムの 1 つの iter のように考えてください。ただし、それらを特別な要素と比較するのではなく、代わりに上記の述語を使用します。

EDIT3:私は遊んで最悪のケースを見つけようとしました(最悪のケースremove_if- 述語が真になることはめったにないことに注意してください)そして私はこれを得ました:

#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

他の用途では、パーティションは少し速く、同じか、遅くなります。私を困惑させてください。:D

4

3 に答える 3

13

私はあなたが現在のstable_removeものであり、実装されるという仮想的な定義について質問していると思いますが、実装者は正しい値を任意の順序で与えることが最善であると考えています。実装者は、とまったく同じことを行うだけで改善できることを期待しています。removeremovestable_remove

実際には、ライブラリはこの最適化を簡単に行うことはできません。データによって異なりますが、各要素を削除する方法を決定する前に、削除する要素の数を計算するのに時間がかかりすぎないようにする必要があります。たとえば、追加のパスを実行してそれらをカウントすることはできますが、その追加のパスが非効率的である場合がたくさんあります。不安定な削除が特定の場合に安定よりも速いからといって、必ずしも2つから選択する適応アルゴリズムが適切であることを意味するわけではありません。

removeとの違いsortは、ソートは多くの異なるソリューションとトレードオフおよび微調整を伴う複雑な問題であることが知られていることだと思います。すべての「単純な」ソートアルゴリズムは平均して遅いです。ほとんどの標準アルゴリズムは非常に単純でありremove、そのうちの1つですが、sortそうではありません。stable_removeしたがって、remove個別の標準関数として定義することはあまり意味がないと思います。

編集:私の微調整(std::partition右側の値を維持する必要はありません)を使用した編集は、私にはかなり合理的なようです。双方向のイテレータが必要ですが、標準には、などのさまざまなイテレータカテゴリで異なる動作をするアルゴリズムの先例がありますstd::distanceunstable_removeしたがって、標準では、フォワードイテレータのみが必要であると定義することは可能ですが、ビディイテレータを取得した場合はそれを実行します。標準はおそらくアルゴリズムをレイアウトしませんが、「イテレータが双方向の場合、削除された要素の数で最大でmin(k, n-k)移動します」のようなフレーズを含めることができ、事実上それを強制します。kただし、標準では現在、移動回数が指定されていないことに注意してくださいremove_ifそうなので、これを固定することは単に優先事項ではなかったと思います。

もちろん、独自の実装を妨げるものは何もありませんunstable_remove

標準が不安定な削除を指定する必要がないことを受け入れる場合、問題は、それが定義する関数が呼び出されるべきかどうかになり、ビディイテレータでは異なる動作をし、フォワードイテレータでは異なる動作をする可能性がstable_removeある将来を予測しますremove不安定な削除を行うための巧妙なヒューリスティックが、標準機能の価値があることが十分に知られるようになった場合。私はそうは言わないでしょう:標準関数の名前が完全に規則的でなくても、それは災害ではありません。STLから安定性の保証を削除することはかなり混乱を招く可能性がありますremove_if。次に、質問は、「なぜSTLはそれを呼び出さなかったのですか?stable_remove_if「すべての回答で指摘されたすべての点に加えて、STLの設計プロセスは標準化プロセスよりも迅速であったと私は答えることができます。

stable_removeまた、理論的には不安定なバージョンを持つ可能性のある他の標準機能に関するワームの缶を開きます。特にばかげた例については、コピー中に要素の順序を逆にすることが明らかに高速な実装が存在する場合に備えて、をcopy呼び出す必要がありますか?実装がどちらを選択し、どちらがより高速であるかに従って呼び出されるように、を呼び出す必要がありますかstable_copy?委員会の仕事の一部は、どこかに線を引くことです。copycopy_forwardcopy_backwardcopy_forwardcopy

stable_remove現実的には、現在の標準は賢明であり、aとaを別々に定義することは賢明だと思いますがremove_with_some_other_constraintsremove_in_some_unspecified_wayそれと同じ最適化の機会を与えるわけではありませんsort_in_some_unspecified_way。イントロソートは、C ++が標準化されたのと同じように、1997年に発明されましたが、周りの研究努力removeがそれまでとまったく同じであるとは思いませんsort。私は間違っているかもしれません、最適化removeは次の大きなことかもしれません、そしてそうなら、委員会はトリックを逃しました。

于 2012-12-11T10:47:32.023 に答える
3

std::remove前方反復子で動作するように指定されています。

最初と最後から反復子のペアを操作するアプローチは、反復子の要件を増やして関数の有用性を低下させるか、漸近的な複雑さの保証に違反するか、悪化させます。

于 2012-12-11T11:15:36.600 に答える
1

>3年後の自分の質問に答えるには:)
はい、「失敗」でした。

を追加する提案D0041R0unstable_removeがあります。std::unstable_remove追加する提案があるからといって、それが間違いだったという意味ではないと主張する人もいるかもしれませんstd::removeが、私は同意しません. :)

于 2016-04-27T19:49:56.297 に答える