web-dev-qa-db-ja.com

消去と削除の違い

Std :: removeアルゴリズムの使用法の違いについて少し混乱しています。具体的には、このアルゴリズムを使用するときに削除されるものを理解できません。このような小さなテストコードを作成しました。

std::vector<int> a;
a.Push_back(1);
a.Push_back(2);

std::remove(a.begin(), a.end(), 1);


int s = a.size();

std::vector<int>::iterator iter = a.begin();
std::vector<int>::iterator endIter = a.end();

std::cout<<"Using iter...\n";
for(; iter != endIter; ++iter)
{
    std::cout<<*iter<<"\n";
}

std::cout<<"Using size...\n";
for(int i = 0; i < a.size(); ++i)
{
    std::cout<<a[i]<<"\n";
}

どちらの場合も出力は2,2でした。

ただし、次のようなremoveでeraseを使用すると、

a.erase(std::remove(a.begin(), a.end(), 1), a.end());

出力は2になります。

だから私の質問は:

(1)。消去機能で使用する以外に、std :: removeの使用はありますか。

(2)。 std :: removeを実行した後でも、a.size()が1ではなく2を返すのはなぜですか?

私は、Scott MeyerのEffective STL bookの消去-削除イディオムに関する項目を読みました。しかし、まだこの混乱があります。

47
Naveen

remove()は実際にはコンテナから要素を削除しません-削除された要素の上にある削除されていない要素を前方にシャントするだけです。重要なのは、remove()がコンテナだけでなく任意の前方イテレータペア:で動作するように設計されていることを認識することです。つまり、できません任意のイテレータペアが必ずしも要素を削除する機能を持っているわけではないため、実際に要素を削除します。

たとえば、通常のC配列の先頭と末尾へのポインタは前方反復子であるため、remove()で使用できます。

_int foo[100];

...

remove(foo, foo + 100, 42);    // Remove all elements equal to 42
_

remove()が配列のサイズを変更できないことは明らかです!

52
j_random_hacker

std::removeは、実際のオブジェクトを削除するのではなく、コンテナの最後にプッシュします。メモリの実際の削除と割り当て解除は、消去によって行われます。そう:

(1)。消去機能で使用する以外に、std :: removeの使用はありますか。

はい、適切な割り当て解除などを心配することなく、イテレータのペアを新しいシーケンスに取得するのに役立ちます。

(2)。 std :: removeを実行した後でも、a.size()が1ではなく2を返すのはなぜですか?

コンテナはまだこれらのオブジェクトを保持しています。作業するイテレータの新しいセットしかありません。したがって、サイズは以前のサイズのままです。

17
dirkgently

std :: removeは何をしますか?

std::removeの擬似コードは次のとおりです。数秒かけてその動作を確認し、説明を読んでください。

Iter remove(Iter start, Iter end, T val) {
    Iter destination = start;

    //loop through entire list
    while(start != end) { 
        //skip element(s) to be removed
        if (*start == val) { 
            start++; 
         }
         else //retain rest of the elements
             *destination++ = *start++;
     }

     //return the new end of the list
     return destination;
}

Removeは単にシーケンス内の要素を上に移動し、削除したい値を上書きすることに注意してください。削除したい値は確かになくなっていますが、問題は何ですか?値が{1、2、3、4、5}のベクターがあるとしましょう。 val = 3に対してremoveを呼び出した後、ベクターは{1、2、4、5、5}になりました。つまり、4と5が上に移動し、3はベクトルから削除されますが、ベクトルのサイズは変更されていません。また、ベクターの最後には、5のコピーが残っています。

vector :: eraseは何をしますか?

std::eraseは、取り除きたい範囲の開始と終了を取ります。 は削除せず、範囲の開始と終了のみを取ります。これがどのように機能するかの擬似コードは次のとおりです。

erase(Iter first, Iter last)
{
    //copy remaining elements from last
    while (last != end())
        *first++ = *last++;

   //truncate vector
   resize(first - begin());
}

したがって、消去操作は実際にコンテナのサイズを変更し、メモリを解放します。

削除消去イディオム

std::removestd::eraseの組み合わせにより、コンテナから一致する要素を削除して、要素が削除された場合にコンテナが実際に切り捨てられるようにすることができます。方法は次のとおりです。

//first do the remove
auto removed = std::remove(vec.begin(), vec.end(), val);

//now truncate the vector
vec.erase(removed, vec.end());

これは、削除消去イディオムとして知られています。なぜこのように設計されているのですか?洞察は、要素を見つける操作はより一般的であり、基礎となるコンテナから独立していることです(反復子にのみ依存します)。ただし、消去の操作は、コンテナがメモリをどのように格納するかによって異なります(たとえば、動的配列の代わりにリンクリストがある場合があります)。そのため、STLは、コンテナが独自の消去を行うと同時に、一般的な「削除」操作を提供することを期待しているため、すべてのコンテナがそのコードを実装する必要はありません。私の見解では、名前は非常に誤解を招くものであり、std::removestd::find_moveと呼ばれるべきでした。

注:上記のコードは厳密に擬似コードです。実際のSTL実装は、たとえば、コピーの代わりにstd::moveを使用するなど、よりスマートです。

14
Shital Shah

私が思いつくことができる最も簡単なもの:

erase()は、コンテナ内の要素に対して実行できることです。コンテナへのイテレータ/インデックスを指定すると、erase( it )はイテレータが参照するものをコンテナから削除します。

remove()は範囲に対してできることで、その範囲を再配置しますが、範囲からは何も消去しません。

6
anon

私は同じ問題に直面し、違いを理解しようとしました。これまでの説明は金銭的に正しいが、例を見て初めて理解した。

#include <algorithm>
#include <string>
#include <iostream>
#include <cctype>

int main()
{
    std::string str1 = "Text with some   spaces";
    std::string::iterator it = remove(str1.begin(), str1.end(), 't');
    std::cout << str1 << std::endl;// prints "Tex wih some   spaceses"
    for (str1.begin();it != str1.end(); ++it) 
    {
         std::cout << *it; //prints "es"
    }

}

ご覧のように、削除は小文字の「t」のみを文字列の最後に移動し、新しいイテレータを新しい文字列の最後に返します(新しい文字列は、削除された要素が挿入されるまでの古い文字列です) )これが、「remove」から取得したイテレータを印刷する理由です

   "Text with some   spaces"
       ^   ^removes both 't', then shift all elements forward -1 //what we want to remove
   "Text with some   spaces"
                          ^ end of string                    -2 //original state of string
   "Tex with some   spacess"
                          ^end of string                     -3 //first 't' removed
   "Tex wih some   spaceses"
                          ^end of string                     -4 //second 't' removed
   "Tex wih some   spaceses"
                        ^new iterator that remove() returned -5 // the state of string after "remove" and without "erase"

ステップ5で取得したイテレータを「erase()」に渡すと、そこから文字列の最後まで消去して、処理中の文字列のサイズを変更することがわかります。

6
Molegrammer

removeは何も削除しないため、「本当に」削除しません。

「実際に」コンテナから要素を削除するには、コンテナAPIにアクセスする必要があります。 as as removeは、イテレーターが指すコンテナーに関係なく、イテレーターでのみ機能します。したがって、removeが「実際の削除」を必要としても、できません。

削除は、削除されなかった次の要素によって「削除された」要素を上書きします。元のendの代わりに、返された新しい論理endを使用するかどうかは呼び出し側が決定します。

あなたの場合、論理的に削除された1vector aから削除しますが、サイズは2のままです。実際に消去すると、ベクターから要素が削除されます。 [ベクターnew endからold endへ]

removeの主なアイデアは、要素の数を変更できず、基準に従って範囲から要素を削除するだけです。

3
aJ.

Vectorのようなコンテナ内のある条件(等しい値またはより小さいなどの条件)を持つ要素を削除するには、関数メンバー関数eraseと_std::remove_または_std::remove_if_を常に結合します。

ベクトルでは、関数eraseは次のように位置ごとに要素を削除するだけです。

反復子消去(反復子の位置);

反復子の消去(最初の反復子、最後の反復子);

しかし、何らかの条件で要素を消去したい場合は、それを_std::remove_または_std::remove_if_と組み合わせることができます。

たとえば、次のベクター内のすべての要素_6_を消去します。

_std::vector<int> vec{6, 8, 10, 3, 4, 5, 6, 6, 6, 7, 8};
// std::remove move elements and return iterator for vector erase funtion
auto last = std::remove(vec.begin(), vec.end(), 6);
for(int a:vec)
    cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 6 6 7 8 

vec.erase(last, vec.end());
for(int a:vec)
    cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 
_

_std::remove_は以下のように機能し、要素を消去せず、要素を移動してイテレータを返します。

enter image description here

可能な実装:

_template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
    first = std::find(first, last, value);
    if (first != last)
        for(ForwardIt i = first; ++i != last; )
            if (!(*i == value))
                *first++ = std::move(*i);
    return first;
}
_

結論:

何らかの条件で要素を削除したい場合、基本的にvector::iterator erase (iterator first, iterator last);を使用します。

最初に範囲の開始を取得します:

auto last = std :: remove(vec.begin()、vec.end()、equal_condition_value);

範囲ごとに消去(常にend()で)

vec.erase(last、vec.end());

引用:

https://en.cppreference.com/w/cpp/algorithm/remove

0
Jayhello