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가 작동하지 않는다는 것은 무엇을 의미합니까?
부지런히

remove_if를 사용하여지도에서 요소를 찾을 수 없습니까? 컴파일 시간 오류가 발생했습니다. 내가 뭔가를 놓치고 있습니까?
aJ.

아니요-remove_if가 시퀀스를 재정렬하여 작동하지 않고 조건에 실패한 요소를 끝으로 이동합니다. 따라서 T [n]에서는 작동하지만 map <T, U>에서는 작동하지 않습니다.
MSalters

2
C + 11을 사용 for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}하면 어수선 함을 줄이는 데 사용할 수 있습니다 . 나머지는 다른 사람들이 말한대로입니다. 이 질문은 ;-) 지금 좀 머리 분할을 저장
아툴 쿠마르에게

답변:


111

거의.

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

원래 가지고 있던 것은 요소를 지우면 반복자를 두 번 증가시킬 것입니다. 지워야하는 요소를 잠재적으로 건너 뛸 수 있습니다.

이것은 여러 곳에서 사용하고 문서화 한 일반적인 알고리즘입니다.

[편집] 지우기 후에 이터레이터가 무효화된다는 것이 정확하지만 지워진 요소를 참조하는 이터레이터 만 다른 이터레이터는 여전히 유효합니다. 따라서 통화 iter++에서 사용 합니다 erase().


4
혼란 스럽습니다. while (...) 대신 for (; ...;)를 사용하는 이유는 무엇입니까? 또한 이것이 작동하는 동안 .erase는 다음 반복자를 반환하지 않습니까? 따라서 if (Some Condition) 블로그는 iter = aMap.erase (iter)가 가장 호환되어야하는 것 같습니다. 아마도 내가 뭔가를 놓치고 있습니까? 여러분 중 일부는 경험이 부족합니다.
Taxilian

86
C ++ 11 map에서는를 포함한 모든 연관 컨테이너 가 erase(iter). 하는 것이 훨씬 더 깨끗합니다 iter = erase( iter ).
Potatoswatter

10
@taxilian (몇 년 늦음) while () 또는 for ()는 작동하지만 의미 상 사람들은 종종 알려진 범위를 반복하는 데 for ()를 사용하고 알 수없는 수의 루프에 대해 while ()을 사용합니다. 이 경우 범위가 (처음부터 endIter까지 ) 알려져 있기 때문에 for ()는 비정상적인 선택이 아니며 아마도 더 일반적 일 것입니다. 그러나 다시, 둘 다 수용 가능할 것입니다.
Jamin Gray

4
@taxilian 더 중요한 것은 'for'를 사용하면 반복기 정의를 루프 범위 내에 포함 할 수 있으므로 나머지 프로그램을 방해하지 않습니다.
Sanchises

1
@athos 질문은 수동태로 표현됩니다. "권장합니다." 보편적 인 권장 사항은 없습니다. 마지막 코멘트가 가장 간단한 방법이라고 생각합니다. 반복자 변수의 두 복사본이 포함되어 누군가 여기서 지적한 것처럼 약간의 효율성을 잃습니다. 당신에게 적합한 것은 당신의 부름입니다.
Potatoswatter 2017-06-06

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 */;
});

두 번째 예 (테스트 값을 전달할 수 있음) :

// '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 ++는 반복자를 증분 한 다음 증분 이전의 요소를 가리키는 반복자를 반환합니다.
Steve Folly

그러나 iter는 여기의 위치에서 지워 지므로 무효화되지 않습니다
1800 INFORMATION

@ 1800INFORMATION : 함수 호출을 입력하는 것은 시퀀스 포인트이며 증가 부작용 erase이 호출 되기 전에 평가 됩니다. 그래서 그들은 실제로 동등합니다. 그래도 원본보다 귀하의 버전을 강력히 선호합니다.
peterchen 2015 년

배열 또는 벡터에서 작동하지만 stl 맵에서 예기치 않은 결과가 발생합니다.
hunter_tech

2

원래 코드에는 한 가지 문제 만 있습니다.

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

여기서는 iterfor 루프에서 한 번 증가하고 지우는 또 다른 시간은 아마도 무한 루프에서 끝날 것입니다.


2

여기에 몇 가지 우아한 해결책이 있습니다.

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

1

하단 노트에서 :

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

쌍 연관 컨테이너는 가변 반복기 (Trivial 반복기 요구 사항에 정의 된대로)를 제공 할 수 없습니다. 변경 가능한 반복기의 값 유형은 할당 가능해야하고 쌍은 할당 가능하지 않기 때문입니다. 그러나 Pair Associative Container는 완전히 상수가 아닌 반복자를 제공 할 수 있습니다. (* i) .second = d 표현식이 유효한 반복자입니다.


1

먼저

맵에는 새 요소를 맵에 삽입해도 기존 요소를 가리키는 반복기가 무효화되지 않는다는 중요한 속성이 있습니다. 맵에서 요소를 지워도 지워지는 요소를 실제로 가리키는 이터레이터를 제외하고는 어떤 이터레이터도 무효화되지 않습니다.

둘째, 다음 코드가 좋습니다.

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

함수를 호출 할 때 해당 함수를 호출하기 전에 매개 변수가 평가됩니다.

따라서 지우기 호출 전에 iter ++를 ​​평가하면 반복기의 ++ 연산자가 현재 항목을 반환하고 호출 후 다음 항목을 가리 킵니다.


1

IMHO에 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

0

Steve Folly의 답변 은 더 효율적이라고 느낍니다.

간단하지만 덜 효율적인 또 다른 솔루션이 있습니다. .

솔루션은 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.