listクラスでリストから要素の削除

http://www.geocities.jp/ky_webid/cpp/library/003.html

listクラスで確保したリストデータを削除するメンバ関数は結構あります。

  • pop_back()・・・末尾の要素を削除
  • pop_front()・・・先頭の要素を削除
  • erase()・・・イテレータによって指定された要素を削除
  • remove()・・・指定した値をもつ全ての要素を削除
  • remove_if()・・・指定した条件に合う全ての要素を削除
  • unique()・・・重複する要素を削除
  • clear()・・・全ての要素を削除


それぞれ動作確認をしてみます。

まずはpop_back関数とpop_front関数です。

#include <iostream>
#include <list>
using namespace std;

int main () {
    list<int> data;
    
    data.push_back(10);
    data.push_back(20);
    data.push_back(30);
    data.push_back(40);
    
    // 末尾の削除
    data.pop_back();
    
    // 先頭の削除
    data.pop_front();
    
    list<int>::iterator it = data.begin();
    
    while( it != data.end() ) {
        cout << *it << endl;
        ++it;
    }
    
    return 0;
}
$ main
20
30

先頭と末尾が削除されてますね。

次はerase関数を使ってイテレータが現在指しているデータを削除してみます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    list<int> data;
    
    data.push_back(10);
    data.push_back(20);
    data.push_back(30);
    data.push_back(40);
    
    list<int>::iterator it = data.begin();
    
    // 二つ進める。データ30を指している状態
    ++it;
    ++it;
    
    // 30の要素を削除
    data.erase(it);
    
    it = data.begin();
    while( it != data.end() ) {
        cout << *it << endl;
        ++it;
    }
    
    return 0;
}
$ main
10
20
40

ちゃんと30が削除されてますね。

次はremove関数です。

これはその値と同じものを発見したら削除するというものです。

例として20という値を持つ要素を削除してみます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    list<int> data;
    
    data.push_back(10);
    data.push_back(20);
    data.push_back(30);
    data.push_back(40);
    
    data.remove(20);
    
    list<int>::iterator it = data.begin();
    
    while( it != data.end() ) {
        cout << *it << endl;
        ++it;
    }
    
    return 0;
}
$ main
10
30
40

20だけが削除されました。

次は少し特殊なremove_if関数についてです。

これは引数に削除条件の処理をする関数のポインタを渡します。

5〜35の値の要素を削除するという処理を例にとってみます。

#include <iostream>
#include <list>
using namespace std;

bool list_delete (const int& it) {
    if ( 5 <= it && it <= 35 ) {
        return true;
    }
    return false;
}

int main () {
    list<int> data;
    
    for(int i=0;i<40;++i){
        data.push_back(i);
    }
    
    data.remove_if(list_delete);
    
    list<int>::iterator it = data.begin();
    
    while( it != data.end() ) {
        cout << *it << endl;
        ++it;
    }
    
    return 0;
}
$ main
0
1
2
3
4
36
37
38
39

バッチリ削除されていますね。

お次は重複した値を削除するunique関数です。

#include <iostream>
#include <list>
using namespace std;

int main () {
    list<int> data;
    
    data.push_back(10);
    data.push_back(20);
    data.push_back(20); // 20が重複
    data.push_back(30);
    data.push_back(40);
    data.push_back(40); // 40も重複
    
    // 重複した値を取り除く
    data.unique();
    
    list<int>::iterator it = data.begin();
    
    while( it != data.end() ) {
        cout << *it << endl;
        ++it;
    }
    
    return 0;
}
$ main
10
20
30
40

いたって簡単ですね。

最後は全削除するclear関数です。まあこれも呼ぶだけなので難しいことは何もないんですが一応実装しみてます。

#include <iostream>
#include <list>
using namespace std;

int main () {
    list<int> data;
    
    data.push_back(10);
    data.push_back(20);
    data.push_back(30);
    data.push_back(40);
    
    cout << data.size() << endl;
    
    // 全削除
    data.clear();
        
    cout << data.size() << endl;
    
    return 0;
}
$ main
4
0

見事に削除されています。

以上要素の削除に関するメンバ関数群でした。

どれも一長一短があって使い分けが必要そうですね。

特にremovie_if関数なんかは利用価値ありそうですけどいちいち条件処理用の関数を用意しておかないといけないので面倒といえば面倒です。

また、ループ中に処理しながら削除したいなんてこともありえそうなのでその場合はerase関数が使えそうですね。

ただし、その12 イテレート中のSTLのlistから要素を安全に削除する方法にもあるようにループ中のerase関数はうまくやらないとハマる可能性があるので注意が必要ですね。

結論的には

list<int>::iterator it;
for ( it=MyList.begin(); it!=MyList.end(); ) {  // <-「it++」を削除
   if( (*it) == 0 ) {
      it = MyList.erase( it );
      continue;
   }
   it++;  // ここで次のイテレートに
}
その12 イテレート中のSTLのlistから要素を安全に削除する方法

このように削除するのが安全だそうです。

僕の場合はwhileでループしてたので自然とこの形になりますね。