反復しながらマップから削除する方法は?


177

反復しながらマップから削除するにはどうすればよいですか?お気に入り:

std::map<K, V> map;
for(auto i : map)
    if(needs_removing(i))
        // remove it from the map

使用map.eraseするとイテレータが無効になります





回答:


279

標準の連想コンテナ消去イディオム:

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

forコンテナー自体を変更しているため、ここでは本当に通常のループが必要です。範囲ベースのループは、要素のみに関心がある場合にのみ使用してください。RBFLの構文は、ループ本体内のコンテナーを公開しないことでもこれを明確にします。

編集。C ++ 11以前では、constiteratorを消去できませんでした。そこであなたは言わなければならないでしょう:

for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }

コンテナから要素を消去することは、要素の一貫性と矛盾しません。類推によって、それは常に定数へのポインタdelete pがどこにあるかpについて完全に正当でした。一定性は寿命を制約しません。C ++のconst値は、既存のものを停止する可能性があります。


1
「ループ本体内のコンテナを公開することすらありません」とはどういう意味ですか?
ダニ

2
@ダニ:さて、これを20世紀の構造と対比してみましょうfor (int i = 0; i < v.size(); i++)。ここではv[i]、ループの中で言う必要があります。つまり、明示的にコンテナーに言及する必要があります。一方、RBFLは、値として直接使用できるループ変数を導入しているため、ループ内でコンテナーの知識は必要ありません。これは、コンテナーについて知る必要がない RBFL forループの使用目的の手がかりです。消去は完全に反対の状況であり、すべてはコンテナーに関するものです。
Kerrek SB、

3
@skyhisi:確かに。これは、ポストインクリメントの正当な使用法の1つです。最初にインクリメントitして次の有効なイテレータを取得し、次に古いイテレータ消去します。それは逆に機能しません!
Kerrek SB、2011

5
私はC ++ 11でit = v.erase(it);マップでも機能することをどこかで読みました。つまり、すべての関連要素のerase()が次の反復子を返すようになりました。そのため、delete()内でpost-increment ++を必要とした古いクラッジは不要になりました。これは(trueの場合)良いことです。kludgeは、オーバーライドされたpost-increment-within-a-function-callマジックに依存していたため、初心者のメンテナーによって「修正」されて、関数呼び出しから増分を取り除いたり、入れ替えたりしています。 「それは単なるスタイルの問題だから」などの前増分
Dewi Morgan

3
ブロックを呼び出すのit++はなぜですか?これらのに一度呼び出すだけで十分ではないでしょうか?if else
nburk

25

私は個人的に、このパターンを好みます。これは、追加の変数を犠牲にして、少し明確で単純です。

for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
  ++next_it;
  if (must_delete)
  {
    m.erase(it);
  }
}

このアプローチの利点:

  • forループの増分は、増分として意味があります。
  • 消去操作は、インクリメントロジックと混合されるのではなく、単純な消去です。
  • ループ本体の最初の行の後、意味itnext_it反復全体を通して固定されたままになり、意図したとおりに機能するかどうかを間違えることなく、それらを参照する追加のステートメントを簡単に追加できます(もちろん、それをit消去した後に使用することはできません) 。

2
実際に別の利点を考えることができます。ループが、反復されているエントリまたは以前のエントリを消去するコードを呼び出した場合(ループがそれに気付かない場合)、害を及ぼすことなく機能します。唯一の制限は、何かがnext_itまたはその後継者によって指摘されているものを消去しているかどうかです。完全にクリアされたリスト/マップもテストできます。
Larswad 2017年

ループがより複雑で、削除するかどうか、または他のさまざまなタスクを実行するかどうかを決定するロジックの複数のレベルがある場合でも、この答えは単純で明確です。ただし、少し簡単にするために編集を提案しました。タイプミスを回避するために、forのinitで「next_it」を「it」に設定できます。また、initステートメントと反復ステートメントの両方で、それとnext_itを同じ値に設定するため、「next_it = it;」と言う必要はありません。ループの開始時。
cdgraham 2018年

1
この回答を使用する人は誰でも覚えておいてください。「++ next_it」は、反復式ではなく、forループ内にある必要があります。"it = next_it ++"として反復式に移動しようとすると、最後の反復で、 "it"が "m.cend()"と等しく設定されるときに、 "next_it"を反復しようとします。過去の「m.cend()」は誤りです。
cdgraham 2018年

6

要約すると、「マップを反復しながらマップから削除するにはどうすればよいですか?」

  • 古いマップ実装では:できません
  • 新しいマップの実装:ほとんど@KerrekSBが示唆したとおり。しかし、彼が投稿したものにはいくつかの構文の問題があります。

GCCマップimplから(GXX_EXPERIMENTAL_CXX0Xに注意):

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *  @return An iterator pointing to the element immediately following
       *          @a position prior to the element being erased. If no such 
       *          element exists, end() is returned.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      iterator
      erase(iterator __position)
      { return _M_t.erase(__position); }
#else
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      void
      erase(iterator __position)
      { _M_t.erase(__position); }
#endif

古いスタイルと新しいスタイルの例:

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;
typedef map<int, int> t_myMap;
typedef vector<t_myMap::key_type>  t_myVec;

int main() {

    cout << "main() ENTRY" << endl;

    t_myMap mi;
    mi.insert(t_myMap::value_type(1,1));
    mi.insert(t_myMap::value_type(2,1));
    mi.insert(t_myMap::value_type(3,1));
    mi.insert(t_myMap::value_type(4,1));
    mi.insert(t_myMap::value_type(5,1));
    mi.insert(t_myMap::value_type(6,1));

    cout << "Init" << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    t_myVec markedForDeath;

    for (t_myMap::const_iterator it = mi.begin(); it != mi.end() ; it++)
        if (it->first > 2 && it->first < 5)
            markedForDeath.push_back(it->first);

    for(size_t i = 0; i < markedForDeath.size(); i++)
        // old erase, returns void...
        mi.erase(markedForDeath[i]);

    cout << "after old style erase of 3 & 4.." << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    for (auto it = mi.begin(); it != mi.end(); ) {
        if (it->first == 5)
            // new erase() that returns iter..
            it = mi.erase(it);
        else
            ++it;
    }

    cout << "after new style erase of 5" << endl;
    // new cend/cbegin and lambda..
    for_each(mi.cbegin(), mi.cend(), [](t_myMap::const_reference it){cout << '\t' << it.first << '-' << it.second << endl;});

    return 0;
}

プリント:

main() ENTRY
Init
        1-1
        2-1
        3-1
        4-1
        5-1
        6-1
after old style erase of 3 & 4..
        1-1
        2-1
        5-1
        6-1
after new style erase of 5
        1-1
        2-1
        6-1

Process returned 0 (0x0)   execution time : 0.021 s
Press any key to continue.

1
わかりません。何が問題なのmi.erase(it++);ですか?
lvella

1
@lvella opを参照してください。「map.eraseを使用すると、イテレータが無効になります」。
Kashyap

消去後にマップが空になると、新しいメソッドは機能しません。その場合、イテレーターは無効になります。したがって、消去後すぐに挿入することをお勧めしif(mi.empty()) break;ます。
Rahat Zaman

4

C ++ 20ドラフトには、便利な関数が含まれていますstd::erase_if

したがって、その関数を使用して、ワンライナーとしてそれを行うことができます。

std::map<K, V> map_obj;
//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);
//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});

3

かなり悲しいね 私が通常行う方法は、走査中に削除するのではなく、イテレータのコンテナを構築することです。次に、コンテナをループしてmap.erase()を使用します

std::map<K,V> map;
std::list< std::map<K,V>::iterator > iteratorList;

for(auto i : map ){
    if ( needs_removing(i)){
        iteratorList.push_back(i);
    }
}
for(auto i : iteratorList){
    map.erase(*i)
}

しかし、1つを消去した後、残りは無効になります
ダニ


@ダニ:マップではありません。マップの消去は、消去されたアイテムのイテレーターを無効にするだけです。
UncleBens

3

C ++ 11を想定して、プログラミングスタイルと一致している場合は、1行のループ本体があります。

using Map = std::map<K,V>;
Map map;

// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
  itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);

他のいくつかのマイナーなスタイルの変更:

  • 宣言された型を表示(Map::const_iterator)可能な場合/便利な、オーバー使用auto
  • usingテンプレートタイプに使用して、補助タイプ(Map::const_iterator)を読みやすく/維持しやすくします。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.