지도를 반복하면서 제거하는 방법?


177

반복하면서지도에서 어떻게 제거합니까? 처럼:

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

내가 사용 map.erase하면 반복자가 무효화됩니다.



훨씬 더 비슷합니다 : stackoverflow.com/questions/800955/…
Kleist


답변:


280

표준 연관 컨테이너 지우기 관용구 :

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 이전 버전에서는 const-iterator를 지울 수 없습니다. 거기 당신은 말해야 할 것입니다 :

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

컨테이너에서 요소를 지우는 것은 요소의 구성과 상충되지 않습니다. 유추하여, 그것은 항상 포인터를 가리키는 delete p위치 에 완벽하게 합법적 p이었습니다. 일관성은 수명을 제한하지 않습니다. C ++의 const 값은 여전히 ​​기존을 중지 할 수 있습니다.


1
"루프 바디 내부에 용기를 노출시키지 않아도"무슨 뜻입니까?
Dani

2
@Dani : 음, 이것을 20 세기 건축과 대조하십시오 for (int i = 0; i < v.size(); i++). 여기서 우리는 v[i]루프 안에서 말해야 합니다. 즉, 컨테이너를 명시 적으로 언급해야합니다. 반면에 RBFL은 값으로 직접 사용할 수있는 루프 변수를 도입하므로 루프 내부에 컨테이너에 대한 지식이 필요하지 않습니다. 이는 컨테이너에 대해 알 필요가 없는 RBFL for 루프의 의도 된 사용법에 대한 단서 입니다. 지우기는 컨테이너에 관한 모든 반대 상황입니다.
Kerrek SB

3
@skyhisi : 그렇습니다. : 이것은 사후 증가의 합법적 인 용도 중 하나입니다 먼저 증가 it다음, 유효 반복자를 얻을하고 다음 이전 하나를 삭제. 다른 방법으로 작동하지 않습니다!
Kerrek SB

5
C ++ 11에서는 it = v.erase(it);이제 맵에서도 작동 한다는 것을 읽었습니다. 즉 , 모든 연관 요소의 erase () 가 다음 반복자를 반환합니다. 따라서 delete () 내에서 사후 증가 ++가 필요한 오래된 kludge는 더 이상 필요하지 않습니다. 클레어가 함수 호출 내에서 재정의 된 후 증가에 의존하여 초보자 유지 관리자가 "고정"하여 함수 호출에서 증가분을 가져 오거나 스왑하기 때문에이 사실은 참 좋은 일입니다. "단순한 스타일이기 때문에"사전 증가에
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 루프 증분은 증분기로 의미가 있습니다.
  • 소거 동작은 증분 로직과 혼합되지 않고 단순한 소거이다.
  • 루프 본문의 첫 번째 줄 다음에 반복의 의미 it와 의미 next_it가 고정 된 상태로 유지되므로 의도 한대로 작동하는지 여부를 긁지 않고이를 참조하는 추가 명령문을 쉽게 추가 할 수 있습니다 (물론 it삭제 후 사용할 수없는 경우는 제외 ) .

2
루프가 코드를 호출하여 해당 항목이 반복되거나 이전 항목이 지워지고 루프가 인식되지 않는 경우 실제로 다른 이점을 생각할 수 있습니다. 유일한 제한은 next_it 또는 후속 작업이 가리키는 내용을 지우고 있는지 여부입니다. 완전히 지워진 목록 / 맵도 테스트 할 수 있습니다.
Larswad

이 답변은 루프가 더 복잡하고 삭제 또는 다른 다양한 작업을 수행할지 여부를 결정하는 여러 수준의 논리가 있어도 간단하고 명확합니다. 그래도 좀 더 간단하게 편집 할 것을 제안했습니다. "next_it"는 오타를 피하기 위해 for 's init에서 "it"로 설정할 수 있으며, init 및 iteration 문은 모두 동일한 값으로 설정하고 next_it를 설정하므로 "next_it = it;"이라고 말할 필요가 없습니다. 루프가 시작될 때
cdgraham

1
이 답변을 사용하는 사람은 명심하십시오. 반복 표현식이 아니라 for 루프 안에 "++ next_it"가 있어야합니다. "it = next_it ++"로 반복 표현식으로 이동하려고하면 "it"가 "m.cend ()"와 동일하게 설정 될 때 마지막 반복에서 "next_it"를 반복하려고 시도합니다. "m.cend ()"를 지나서 잘못되었습니다.
cdgraham

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)
}

그러나 하나를 지운 후 나머지는 유효하지 않습니다
Dani


@Dani :지도에 없습니다. 맵에서 삭제하면 이터레이터가 지워진 항목에 대해서만 무효화됩니다.
UncleBens

3

C ++ 11을 가정 할 때 프로그래밍 스타일과 일치하는 경우 한 줄짜리 루프 본문이 있습니다.

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.