web-dev-qa-db-ja.com

繰り返しながらstd :: listから要素を削除できますか?

次のようなコードがあります。

for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
    bool isActive = (*i)->update();
    //if (!isActive) 
    //  items.remove(*i); 
    //else
       other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);

リストを再度検索しないようにするために、非アクティブなアイテムを更新後すぐに削除したいと思います。ただし、コメント化された行を追加すると、i++に到達したときに「List iterator not incrementable」というエラーが発生します。 forステートメントで増分しない代替をいくつか試しましたが、何も機能しませんでした。

Std :: listを歩いているときにアイテムを削除する最良の方法は何ですか?

224
AShelly

最初に(i ++を使用して)イテレータをインクリメントしてから、前の要素を削除する必要があります(たとえば、i ++からの戻り値を使用して)。次のようにコードをwhileループに変更できます。

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive)
    {
        items.erase(i++);  // alternatively, i = items.erase(i);
    }
    else
    {
        other_code_involving(*i);
        ++i;
    }
}
263

あなたがしたい:

i= items.erase(i);

これにより、削除したイテレータの後の場所を指すようにイテレータが正しく更新されます。

121
MSN

クリストの答えとMSNの組み合わせを行う必要があります。

// Note: Using the pre-increment operator is preferred for iterators because
//       there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
//       along the way you can safely save end once; otherwise get it at the
//       top of each loop.

std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end  = items.end();

while (iter != items.end())
{
    item * pItem = *iter;

    if (pItem->update() == true)
    {
        other_code_involving(pItem);
        ++iter;
    }
    else
    {
        // BTW, who is deleting pItem, a.k.a. (*iter)?
        iter = items.erase(iter);
    }
}

もちろん、最も効率的でSuperCool®STLに優れているのは次のようなものです。

// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
    if (m_needsUpdates == true)
    {
        m_needsUpdates = other_code_involving(this);
    }

    return (m_needsUpdates);
}

// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));
21
Mike

Std :: remove_ifアルゴリズムを使用します。

編集:コレクションの操作は次のようになります。1.コレクションを準備します。 2.プロセスの収集。

この手順を混ぜないと、生活が楽になります。

  1. std :: remove_if。またはlist :: remove_if(TCollectionではなくlistで作業していることがわかっている場合)
  2. std :: for_each
10
Mykola Golubyev

以下は、リストの走査中にアイテムが削除された場合にリストを繰り返し、イテレータをインクリメントまたは再検証するforループを使用した例です。

for(auto i = items.begin(); i != items.end();)
{
    if(bool isActive = (*i)->update())
    {
        other_code_involving(*i);
        ++i;

    }
    else
    {
        i = items.erase(i);

    }

}

items.remove_if(CheckItemNotActive);
5
David Cormack

クリストの答えのループ版の代替。

いくつかの効率を失い、削除するときに後方に移動してから再び前方に移動しますが、余分なイテレータの増分と引き換えに、ループスコープで宣言されたイテレータとコードが少しきれいに見えるようにすることができます。何を選択するかは、その瞬間の優先度に依存します。

答えは完全に時間外でした、私は知っています...

typedef std::list<item*>::iterator item_iterator;

for(item_iterator i = items.begin(); i != items.end(); ++i)
{
    bool isActive = (*i)->update();

    if (!isActive)
    {
        items.erase(i--); 
    }
    else
    {
        other_code_involving(*i);
    }
}
4
Rafael Gago

あなたは書ける

std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
    bool isActive = (*i)->update();
    if (!isActive) {
        i = items.erase(i); 
    } else {
        other_code_involving(*i);
        i++;
    }
}

std::list::remove_ifを使用して同等のコードを書くことができます。

items.remove_if([] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
});

std::vector::erasestd::remove_ifイディオムは、アイテムがO(n)での互換性を維持するためにリストではなくベクトルの場合に使用する必要があります。単一アイテム(ベクターなど)

items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
    bool isActive = (*i)->update();
    if (!isActive) 
        return true;

    other_code_involving(*i);
    return false;
}));
2
J. Kalz

削除は、削除される要素を指す反復子のみを無効にします。

したがって、この場合、* iを削除した後、iは無効になり、インクリメントすることはできません。

あなたができることは、最初に削除する要素のイテレータを保存し、次にイテレータをインクリメントしてから保存したイテレータを削除することです。

2
anand

std::listをキューのように考えると、保持するすべてのアイテムをデキューおよびキューに登録できますが、削除するアイテムのみをデキュー(エンキューではなく)できます。ここに、1〜10の数字を含むリストから5を削除する例を示します。

std::list<int> myList;

int size = myList.size(); // The size needs to be saved to iterate through the whole thing

for (int i = 0; i < size; ++i)
{
    int val = myList.back()
    myList.pop_back() // dequeue
    if (val != 5)
    {
         myList.Push_front(val) // enqueue if not 5
    }
}

myListには、1〜4および6〜10の番号のみが追加されます。

2
Alex Bagg

私はそれを要約しています、ここに例の3つの方法があります:

1. whileループを使用する

list<int> lst{4, 1, 2, 3, 5};

auto it = lst.begin();
while (it != lst.end()){
    if((*it % 2) == 1){
        it = lst.erase(it);// erase and go to next
    } else{
        ++it;  // go to next
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

2.リストでremove_ifメンバー機能を使用する:

list<int> lst{4, 1, 2, 3, 5};

lst.remove_if([](int a){return a % 2 == 1;});

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

3. eraseメンバー関数と組み合わせたstd::remove_if funtionの使用:

list<int> lst{4, 1, 2, 3, 5};

lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
    return a % 2 == 1;
}), lst.end());

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2

4. for loopを使用して、イテレータの更新に注意する必要があります。

list<int> lst{4, 1, 2, 3, 5};

for(auto it = lst.begin(); it != lst.end();++it){
    if ((*it % 2) == 1){
        it = lst.erase(it);  erase and go to next(erase will return the next iterator)
        --it;  // as it will be add again in for, so we go back one step
    }
}

for(auto it:lst)cout<<it<<" ";
cout<<endl;  //4 2 
2
Jayhello

後方に反復することで、横断される残りの要素の要素を消去する効果を回避できます。

typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
    --it;
    bool remove = <determine whether to remove>
    if ( remove ) {
        items.erase( it );
    }
}

PS: this を参照してください(例:後方反復に関して)。

PS2:最後の要素を適切に消去するかどうかを徹底的にテストしませんでした。

1
sancho.s