std :: mapと同等のremove_if


118

特定の条件に基づいて、マップからさまざまな要素を削除しようとしました。STLアルゴリズムを使用してどうすればよいですか?

最初は使用を考えましたがremove_if、連想コンテナに対してremove_ifが機能しないため、それは不可能です。

マップで機能する「remove_if」と同等のアルゴリズムはありますか?

簡単なオプションとして、マップをループして消去することを考えました。しかし、マップをループして安全なオプションを消去していますか?(イテレータは消去後に無効になるため)

私は次の例を使用しました:

bool predicate(const std::pair<int,std::string>& x)
{
    return x.first > 2;
}

int main(void) 
{

    std::map<int, std::string> aMap;

    aMap[2] = "two";
    aMap[3] = "three";
    aMap[4] = "four";
    aMap[5] = "five";
    aMap[6] = "six";

//      does not work, an error
//  std::remove_if(aMap.begin(), aMap.end(), predicate);

    std::map<int, std::string>::iterator iter = aMap.begin();
    std::map<int, std::string>::iterator endIter = aMap.end();

    for(; iter != endIter; ++iter)
    {
            if(Some Condition)
            {
                            // is it safe ?
                aMap.erase(iter++);
            }
    }

    return 0;
}

remove_ifが機能しないとはどういう意味ですか?
2009

remove_ifを使用してマップ内の要素を見つけることはできませんよね?コンパイル時エラーが発生しました。何か不足していますか?
aJ。

いいえ、シーケンスを並べ替えてremove_ifが機能するため、条件を満たさない要素を最後に移動します。したがって、T [n]では機能しますが、map <T、U>では機能しません。
MSalters 2009

2
C + for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}111を使用すると、乱雑さを減らすことができます。残りは他が言ったようです。この質問は、私に髪の毛を少しだけ割くのを助けました;-)
Atul Kumar

回答:


111

ほとんど。

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

もともとイテレータから要素を削除した場合、イテレータを2回インクリメントしていました。消去する必要がある要素をスキップする可能性があります。

これは、私が多くの場所で使用および文書化したことを見た一般的なアルゴリズムです。

[編集]イテレータは消去後に無効化されるのは正しいですが、消去される要素を参照するイテレータのみであり、他のイテレータは引き続き有効です。したがってiter++erase()呼び出しで使用します。


4
よくわかりません; なぜwhile(...)ではなくfor(; ...;)を使用するのですか?また、これはおそらく機能しますが、.eraseは次のイテレータを返しませんか?したがって、(Some Condition)ブログを最も互換性のあるものにするには、iter = aMap.erase(iter)にする必要があるようです。おそらく何かが足りないのでしょうか?私はあなたの一部が持っている経験に欠けています。
タクシー運転手、2011

86
C ++ 11では、を含むすべての連想コンテナがmapから次のイテレータを返しますerase(iter)。実行する方がずっとクリーンですiter = erase( iter )
Potatoswatter

10
@taxilian(数年遅れ)while()またはfor()は機能しますが、意味的には、既知の範囲を反復するためにfor()を使用し、不明な数のループに対してwhile()を使用することがよくあります。この場合、範囲は(最初からendIterまで既知であるため、for()は珍しい選択ではなく、おそらくより一般的です。しかし、繰り返しになりますが、両方とも許容されます。
Jamin Grey 2013

4
@taxilianさらに重要なことに、 'for'を使用すると、ループのスコープ内でイテレーターを定義できるため、プログラムの残りの部分が混乱することはありません。
Sanchises 2014年

1
@athos質問は受身の声で表現され、「推奨されています」。普遍的な推奨事項はありません。私の最後のコメントは最も簡単な方法だと思います。これには、反復子変数の2つのコピーが含まれます。これは、誰かがここで指摘したように少し効率が低下します。あなたにとって適切なのはあなたの電話です。
Potatoswatter 2017

75

std :: map(およびその他のコンテナー)のerase_if

私はこのために次のテンプレートを使用します。

namespace stuff {
  template< typename ContainerT, typename PredicateT >
  void erase_if( ContainerT& items, const PredicateT& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

これは何も返しませんが、項目をstd :: mapから削除します。

使用例:

// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
  return /* insert appropriate test */;
});

2番目の例(テスト値を渡すことができます):

// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4;  // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
  return item.property < test_value;  // or whatever appropriate test
});

3
@CodeAngryありがとう-これはまだに存在していなかったのでいつも奇妙に思えましたstd。なぜのメンバーではないのかは理解できstd::mapますが、標準ライブラリにはこのようなものが必要だと思います。
Iron Savior

3
その他のC ++ 20でstd::map追加されます。
Roi Danton


3

このドキュメントは、優れたSGI STLリファレンスから入手しまし

マップには、新しい要素をマップに挿入しても、既存の要素を指すイテレータが無効にならないという重要な特性があります。マップから要素を消去しても、イテレータが無効になることはありません。ただし、消去される要素を実際に指しているイテレータは除きます。

したがって、消去する要素を指しているイテレータはもちろん無効になります。このようなことをしてください:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
これは元のコードと同じです。iter ++はイテレータをインクリメントし、インクリメントの前に要素を指すイテレータを返します。
スティーブフォリー

ただし、ここの位置で消去するため、イターは無効になりません
1800情報

@ 1800INFORMATION:関数呼び出しの入力はシーケンスポイントであり、erase呼び出される前に増分副作用が評価されます。したがって、それらは実際には同等です。それでも、私はオリジナルよりもあなたのバージョンを強く望んでいます。
peterchen 2015年

これは配列またはベクトルで機能しますが、stlマップで予期しない結果を引き起こします。
hunter_tech

2

元のコードには1つだけ問題があります。

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

ここでiterは、forループで1回インクリメントされ、eraseでもう1回インクリメントされます。これにより、おそらく無限ループが発生します。


2

ここにいくつかのエレガントなソリューションがあります。

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}

1

以下のメモから:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

可変連想配列の値の型は割り当て可能である必要があり、ペアは割り当て可能ではないため、ペア連想コンテナは可変反復子を提供できません(トリビアル反復子の要件で定義されています)。ただし、ペア連想コンテナは、完全に一定ではないイテレータを提供できます。式(* i).second = dが有効であるようなイテレータです。


1

最初

マップには、新しい要素をマップに挿入しても、既存の要素を指すイテレータが無効にならないという重要な特性があります。マップから要素を消去しても、イテレータが無効になることはありません。ただし、イテレータが実際に消去されている要素を指している場合は除きます。

第二に、次のコードは良いです

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

関数を呼び出すとき、パラメーターはその関数の呼び出し前に評価されます。

したがって、eraseを呼び出す前にiter ++が評価されると、イテレータの++演算子は現在のアイテムを返し、呼び出し後に次のアイテムをポイントします。


1

私見ではremove_if()同等のものはありません。
マップを並べ替えることはできません。
ですからremove_if()、あなたが興味のあるペアをあなたが呼ぶことができる最後に置くことはできませんerase()


それは本当に残念です。
allyourcode 2014

1

Iron Saviorの回答に基づく。

template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
    for (; it != Last; ) {
        if (Pred(*it)) it = items.erase(it);
        else ++it;
    }
}

ContainerTアイテムを失い、それをイテレータから取得する方法があるかどうか知りたいです。


1
「アンダースコアで始まり、その後に大文字が続く識別子は、実装によるすべての使用のために予約されています。」
YSC 2017年

0

スティーブフォリーの答え私はより効率的に感じます。

簡単だが効率の悪い別のソリューションを次に示します

ソリューションはremove_copy_if、使用する値を新しいコンテナーにコピーするために使用し、次に、元のコンテナーの内容を新しいコンテナーの内容と交換します。

std::map<int, std::string> aMap;

...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;

//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(), 
                    inserter(aTempMap, aTempMap.end()),
                    predicate);

//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);

2
それは非効率的です。
allyourcode 2014

0

2より大きいキーを持つすべての要素を消去したい場合、最良の方法は

map.erase(map.upper_bound(2), map.end());

ただし、範囲に対してのみ機能し、述部には機能しません。


0

私はこのように使います

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.