web-dev-qa-db-ja.com

ベクトルのstd :: removeとeraseの違いは?

私は頭の中で明確にしたいのか疑問です。 erasestd::vectorの間のstd::removeの異なる動作を認識しています。最初の場合はベクトルから要素を物理的に削除し、サイズを縮小します。同じ。

これは効率上の理由だけですか? eraseを使用すると、std::vector内のすべての要素が1シフトされ、大量のコピーが発生します。 std::removeは単に「論理的な」削除を行い、移動することでベクトルを変更しないままにします。オブジェクトが重い場合、その違いが問題になる可能性がありますよね?

これは効率上の理由だけですか? eraseを使用すると、std :: vector内のすべての要素が1シフトされ、大量のコピーが発生します。 std :: removeは単に「論理的な」削除を行い、移動することでベクトルを変更しないままにします。オブジェクトが重い場合、その違いは重要ですよね?

このイディオムを使用する理由はまさにそれです。パフォーマンスにはメリットがありますが、単一の消去の場合にはメリットがありません。重要なのは、ベクターから複数の要素を削除する必要がある場合です。この場合、_std::remove_はそれぞれの削除されない要素をその最終位置に一度だけコピーしますが、_vector::erase_アプローチはすべての要素を位置から最後まで移動します複数回。考慮してください:

_std::vector<int> v{ 1, 2, 3, 4, 5 };
// remove all elements < 5
_

ベクトルを1つずつ要素を削除していくと、1が削除され、残りの要素のコピーがシフトされます(4)。次に、2を削除し、残りのすべての要素を1つシフトします(パターンが表示される場合、これはO(N^2)アルゴリズムです。

_std::remove_の場合、アルゴリズムは読み取りヘッドと書き込みヘッドを維持し、コンテナーを反復処理します。最初の4つの要素では、読み取りヘッドが移動され、要素がテストされますが、要素はコピーされません。 5番目の要素についてのみ、オブジェクトは最後から最初の位置にコピーされ、アルゴリズムは1つのコピーで完了し、イテレーターを2番目の位置に返します。これはO(N)アルゴリズムです。範囲を指定した後の_std::vector::erase_は、残りのすべての要素を破壊し、コンテナのサイズを変更します。

他の人が述べたように、標準ライブラリでは、アルゴリズムは反復子に適用され、反復されるシーケンスの知識が不足しています。この設計は、アルゴリズムがコンテナを認識する他のアプローチよりも柔軟性があり、アルゴリズムの単一の実装をイテレータ要件に準拠する任意のシーケンスで使用できます。たとえば、_std::remove_copy_if_を考えてください。コンテナを使用せずに、シーケンスを生成/受け入れるイテレータを使用することで使用できます。

_std::remove_copy_if(std::istream_iterator<int>(std::cin),
                    std::istream_iterator<int>(),
                    std::ostream_iterator<int>(std::cout, " "),
                    [](int x) { return !(x%2); } // is even
                    );
_

この1行のコードは、すべての数値をコンテナのメモリにロードすることなく、標準入力からのすべての偶数をフィルタリングして標準出力にダンプします。これは分割の利点であり、欠点はアルゴリズムがコンテナ自体を変更できず、反復子によって参照される値のみを変更できないことです。

std::removeは、STLのアルゴリズムであり、コンテナにとらわれません。何らかの概念が必要ですが、本当ですが、サイズが静的なC配列でも動作するように設計されています。

8
yves Baumes

_std::remove_は、新しいend()イテレータを返すだけで、最後の削除されていない要素の1つを指します(戻り値からend()へのアイテムの数は、削除するアイテムですが、それらの値が削除するアイテムと同じであるという保証はありません-それらは有効だが指定されていない状態です)。これは、複数のコンテナータイプ(基本的に、ForwardIteratorが反復処理できる任意のコンテナータイプ)で機能するように行われます。

_std::vector::erase_は、サイズを調整した後、実際に新しいend()イテレーターを設定します。これは、vectorのメソッドがイテレータの調整を実際に処理する方法を知っているためです(_std::list::erase_、_std::deque::erase_などでも同様です)。

removeは、指定されたコンテナを整理して、不要なオブジェクトを削除します。コンテナの消去機能は、実際にコンテナが行う必要がある方法で「削除」を処理します。それが彼らが分離している理由です。

6
Zac Howland

サイズを変更するには、ベクター自体に直接アクセスする必要があると思います。 std :: removeはイテレータのみにアクセスできるため、ベクトルに「ちょっと、要素が少なくなった」と伝える方法はありません。

Std :: removeがこのように設計されている理由については、yves Baumesの回答をご覧ください。

5

はい、それが要点です。 eraseは、パフォーマンス特性が異なる他の標準コンテナでもサポートされていることに注意してください(例: list :: erase is O(1))、std::removeはコンテナです-agnosticであり、あらゆるタイプの forward iterator で動作します(したがって、ベアアレイなどでも動作します)。

4
Jon

やや。削除などのアルゴリズムは、イテレータ(コレクション内の要素を表す抽象化)で動作します。これらのアルゴリズムは、操作しているコレクションのタイプを必ずしも認識しないため、コレクションのメンバーを呼び出して実際の削除を行うことはできません。

これは、アルゴリズムが任意のコンテナおよびコレクション全体のサブセットである範囲で一般的に機能するためです。

また、あなたが言うように、パフォーマンスのために、別のアルゴリズムに渡すために論理的な終了位置にアクセスするだけであれば、要素を実際に削除(および破壊)する必要はないかもしれません。

0
Duncan Smith

標準ライブラリアルゴリズムはsequencesで動作します。シーケンスは、イテレーターのペアで定義されます。最初のポイントはシーケンスの最初の要素を指し、2番目のポイントはシーケンスの最後の1つを指します。それで全部です;アルゴリズムは、シーケンスの出所を気にしません。

標準ライブラリコンテナはデータ値を保持し、アルゴリズムで使用するシーケンスを指定するイテレータのペアを提供します。また、コンテナの内部データ構造を利用することで、アルゴリズムと同じ操作をより効率的に実行できるメンバー関数も提供します。

0
Pete Becker

理解を深めるために、次のコードを試してください。

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
const auto newend (remove(begin(v), end(v), 2));

for(auto a : v){
    cout << a << " ";
}
cout << endl;
v.erase(newend, end(v));
for(auto a : v){
    cout << a << " ";
}
0
RLT