std :: map 삽입 또는 std :: map 찾기?


92

기존 항목을 보존하려는 맵을 가정합니다. 20 %의 경우 삽입하는 항목은 새 데이터입니다. 반환 된 반복자를 사용하여 std :: map :: find 다음 std :: map :: insert를 수행하는 것이 장점이 있습니까? 아니면 삽입을 시도한 다음 반복자가 레코드가 삽입되었는지 여부를 나타내는 지 여부에 따라 작업하는 것이 더 빠릅니까?


4
나는 수정되었고 std :: map :: find 대신 std :: map :: lower_bound를 사용하려고했습니다.
Superpolock 2009-08-07

답변:


148

대답은 당신도 마찬가지입니다. 대신 당신의 항목 (24)에 의해 제안 뭔가하고 싶은 효과적인 STL 에 의해 스콧 마이어스를 :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

2
이것이 실제로 find가 작동하는 방식입니다. 트릭은 find 및 insert에 필요한 검색을 결합한다는 것입니다. 물론 삽입을 사용한 다음 두 번째 반환 값을 보는 것도 마찬가지입니다.
puetzk

1
두 가지 질문 : 1) lower_bound를 사용하는 것이지도에서 찾기를 사용하는 것과 어떻게 다른가요? 2) '지도'의 경우 'lb! = mymap.end ()'일 때 &&의 오른손 연산이 항상 참인 경우가 아닙니까?
Richard Corden

12
@Richard : find ()는 키가 없으면 end ()를 반환하고, lower_bound는 항목이 있어야하는 위치를 반환합니다 (이는 삽입 힌트로 사용할 수 있음). @puetzek : 기존 키에 대한 참조 값을 "삽입 만"하면 덮어 쓰지 않습니까? OP가 그것을 원하는지 확실하지 않습니다.
peterchen

2
unorder_map과 비슷한 것이 있는지 아는 사람이 있습니까?
Giovanni Funchal

3
@peterchen map :: insert는 기존 값이있는 경우 덮어 쓰지 않습니다 . cplusplus.com/reference/map/map/insert를 참조 하세요 .
Chris Drew

11

이 질문에 대한 대답은 맵에 저장하는 값 유형을 만드는 데 드는 비용에 따라 다릅니다.

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

int와 같은 값 유형의 경우 위의 방법은 컴파일러 최적화가없는 경우 찾기 뒤에 삽입하는 것보다 더 효율적입니다. 위에서 언급했듯이지도를 통한 검색은 한 번만 이루어지기 때문입니다.

그러나 삽입을 호출하려면 새 "값"이 이미 생성되어 있어야합니다.

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

'삽입'을 호출하기 위해 우리는 값 유형을 구성하기위한 값 비싼 호출에 대해 지불하고 있습니다. 질문에서 말한 내용에 따라이 새로운 값을 20 % 사용하지 않을 것입니다. 위의 경우 맵 값 유형 변경이 옵션이 아닌 경우 먼저 '찾기'를 수행하여 요소를 구성해야하는지 확인하는 것이 더 효율적입니다.

또는 선호하는 스마트 포인터 유형을 사용하여 데이터 핸들을 저장하도록 맵의 값 유형을 변경할 수 있습니다. 삽입 호출은 널 포인터를 사용하며 (구성하기 매우 저렴) 필요한 경우에만 새 데이터 유형이 생성됩니다.


8

2 사이에는 속도 차이가 거의 없으며 find는 반복자를 반환하고 insert는 동일한 작업을 수행하며 어쨌든 항목이 이미 존재하는지 확인하기 위해 맵을 검색합니다.

그래서 .. 개인 취향에 달려 있습니다. 나는 항상 삽입하고 필요한 경우 업데이트를 시도하지만 일부 사람들은 반환되는 쌍을 처리하는 것을 좋아하지 않습니다.


5

찾기 후 삽입하면 키를 찾지 못하고 삽입을 수행하면 추가 비용이 발생할 것이라고 생각합니다. 그것은 일종의 알파벳 순서로 책을 훑어보고 책을 찾지 못하고 책을 다시 훑어보고 삽입 할 위치를 확인하는 것과 같습니다. 키를 어떻게 처리할지, 키가 지속적으로 변경되는지에 따라 결정됩니다. 이제 찾을 수없는 경우 로그, 예외, 원하는 모든 작업을 수행 할 수있는 유연성이 있습니다.


1

효율성이 걱정된다면 hash_map <> 을 확인하십시오 .

일반적으로 map <>은 이진 트리로 구현됩니다. 필요에 따라 hash_map이 더 효율적일 수 있습니다.


사랑했을 것입니다. 그러나 C ++ 표준 라이브러리에는 hash_map이 없으며 PHB는 그 밖의 코드를 허용하지 않습니다.
Superpolock

1
std :: tr1 :: unordered_map 은 다음 표준에 추가되도록 제안 된 해시 맵이며 STL의 최신 구현에서 사용할 수 있어야합니다.
beldaz 2010 년

1

나는 코멘트를 남길만큼 충분한 포인트가없는 것 같지만, 틱된 대답은 나에게 오래 걸린 것 같다. 어쨌든 인서트가 이터레이터를 반환한다고 생각할 때, 반환 된 이터레이터를 사용할 수있을 때 왜 lower_bound를 검색 하느냐. 이상한.


1
insert를 사용한다는 것은 (확실히 C ++ 11 이전) std::map::value_type객체 를 생성해야한다는 것을 의미하기 때문에 허용되는 대답은 그것을 피합니다.
KillianDS

-1

효율성에 대한 답변은 STL의 정확한 구현에 따라 달라집니다. 확실히 알 수있는 유일한 방법은 두 가지 방법으로 벤치마킹하는 것입니다. 차이가 크지 않을 것 같으므로 선호하는 스타일에 따라 결정하십시오.


1
이것은 정확히 사실이 아닙니다. STL은 대부분의 작업에 대해 명시적인 big-O 요구 사항을 제공한다는 점에서 다른 대부분의 라이브러리와 다릅니다. 함수가 해당 O (log n) 동작을 달성하기 위해 사용하는 구현에 관계없이 2 * O (log n) 및 1 * O (log n) 사이에는 보장 된 차이가 있습니다. 그 차이가 플랫폼에서 중요한지 여부 는 다른 질문입니다. 그러나 그 차이는 항상 존재합니다.
srm

big-O 요구 사항을 정의하는 @srm은 여전히 ​​작업이 절대적으로 걸리는 시간을 알려주지 않습니다. 당신이 말하는 보장 된 차이는 존재하지 않습니다.
Mark Ransom 2013

-2

map [key]-stl이 그것을 분류하도록합니다. 그것은 당신의 의도를 가장 효과적으로 전달하는 것입니다.

예, 충분히 공평합니다.

찾기를 한 다음 삽입을 수행하면 누락이 발생했을 때 2 x O (log N)를 수행하는 것입니다. . 곧바로 삽입하고 결과를 검토하는 것이 내가 갈 길입니다.


아니요, 항목이 있으면 기존 항목에 대한 참조를 반환합니다.
Kris Kumler

2
이 답변은 -1입니다. Kris K가 말했듯이 map [key] = value를 사용하면 질문에 필요한대로 "보존"하지 않고 기존 항목을 덮어 씁니다. map [key]를 사용하여 존재 여부를 테스트 할 수 없습니다. 키가 없으면 기본 생성 된 객체를 반환하고 키의 항목으로 생성하기 때문입니다
netjeff

요점은지도가 이미 채워져 있는지 테스트하고지도가없는 경우에만 추가 / 덮어 쓰는 것입니다. map [key]를 사용하면 값이 항상 존재한다고 가정합니다.
srm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.