std :: map insertまたはstd :: map find?


90

既存のエントリを保持したいマップを想定しています。20%の確率で、挿入するエントリは新しいデータです。返されたイテレータを使用してstd :: map :: findを実行してからstd :: map :: insertを実行する利点はありますか?または、挿入を試みてから、レコードが挿入されたか、挿入されなかったかをイテレータが示すかどうかに基づいて動作する方が速いですか?


4
私は修正され、std :: map :: findの代わりにstd :: map :: lower_boundを使用するつもりでした。
Superpolock

回答:


147

答えはどちらもしないことです。代わりに、Scott MeyersによるEffective STLの Item 24で提案されていることを実行したいとします。

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で必要な検索を組み合わせていることです。もちろん、挿入を使用して2番目の戻り値を調べるだけです。
puetzk 2008

1
2つの質問:1)マップの検索を使用する場合とlower_boundを使用する場合の違いは何ですか?2)「マップ」の場合、「lb!= mymap.end()」のときに&&の右側の演算子が常にtrueになるわけではありませんか?
Richard Corden

11
@Richard:キーが存在しない場合、find()はend()を返します。lower_boundは、アイテムが存在するはずの位置を返します(順番に挿入ヒントとして使用できます)。@puetzek:既存のキーのリファレント値を「挿入」するだけで上書きされませんか?OPがそれを望んでいるかどうかはわかりません。
peterchen 2009年

2
unordered_mapに同様のものがあるかどうかは誰でも知っていますか?
ジョバンニフンシャル

3
それが存在する場合は@peterchenマップ::インサートを参照、既存の値を上書きすることはありません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.second->second = v;
  }
}

intなどの値型の場合、上記の方法は、検索の後に挿入を続けるよりも効率的です(コンパイラーの最適化がない場合)。上述のように、これはマップを介した検索が一度だけ行われるためです。

ただし、insertを呼び出すには、新しい「値」がすでに作成されている必要があります。

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.second->second = v;
  }
}

「挿入」を呼び出すために、私たちは値型を構築するための高額な呼び出しに料金を払っています-そして、あなたが質問で言ったことから、この新しい値を20%の時間使用することはありません。上記の場合、マップ値タイプの変更がオプションではない場合、最初に「検索」を実行して、要素を作成する必要があるかどうかを確認する方が効率的です。

または、マップの値タイプを変更して、お気に入りのスマートポインタータイプを使用してデータへのハンドルを保存することもできます。insertの呼び出しはnullポインターを使用し(非常に安価に作成できます)、必要な場合にのみ、新しいデータ型が作成されます。


8

2の間の速度の違いはほとんどありません。findはイテレータを返し、insertも同じことを行い、マップを検索して、エントリがすでに存在するかどうかを判断します。

だから、それは個人の好みによる。常に挿入を試み、必要に応じて更新しますが、返されたペアを処理するのを嫌う人もいます。


5

検索してから挿入を行うと、キーが見つからず、後で挿入を実行したときに、追加のコストがかかると思います。これは、本をアルファベット順に調べて本を見つけるのではなく、本をもう一度調べて、どこに挿入するかを確認するようなものです。つまり、キーをどのように処理するか、そしてキーが常に変化するかどうかです。今では、柔軟性があり、見つからない場合は、ログに記録して、例外を作成し、好きなことを行うことができます...


3

私はトップの答えで迷っています。

Findは、何も見つからない場合にmap.end()を返します。つまり、新しいものを追加する場合

iter = map.find();
if (iter == map.end()) {
  map.insert(..) or map[key] = value
} else {
  // do nothing. You said you did not want to effect existing stuff.
}

の2倍遅い

map.insert

2回検索する必要があるため、まだマップにない要素の場合。そこにあるかどうかを確認し、もう一度新しいものを置く場所を見つけます。


1
STL挿入の1つのバージョンは、イテレーターとブールを含むペアを返します。boolは、見つかったかどうかを示し、イテレータは見つかったエントリか挿入されたエントリのどちらかです。これは効率のために打ち勝つことは困難です。不可能だと思います。
ザンリンクス

4
いいえ、確認答えが使用されlower_bound、ありませんfind。その結果、キーが見つからなかった場合は、イテレータが末尾ではなく挿入ポイントに返されました。その結果、それはより高速です。
Steven Sudit、

1

効率が気になる場合は、hash_map <>をチェックしてください。

通常、map <>はバイナリツリーとして実装されます。必要に応じて、hash_mapの方が効率的です。


大好きだっただろう。しかし、C ++標準ライブラリにはhash_mapがなく、PHBはそれ以外のコードを許可していません。
Superpolock

1
std :: tr1 :: unordered_mapは、次の標準への追加が提案されているハッシュマップであり、STLの現在のほとんどの実装内で使用できる必要があります。
beldaz

1

私はコメントを残すのに十分なポイントがないようですが、チェックされた答えは私に長い間巻き込まれているようです-挿入がとにかくイテレータを返すと考えるとき、返されたイテレータをそのまま使用できるのに、なぜlower_boundを検索するのですか?奇妙な。


1
(確かにC ++ 11より前の)挿入を使用すると、std::map::value_typeオブジェクトを作成する必要があることを意味するため、受け入れられた回答はそれを回避します。
KillianDS 2014年

-1

効率に関する答えは、STLの正確な実装によって異なります。確実に知る唯一の方法は、それを両方の方法でベンチマークすることです。大きな違いはないと思いますので、好みのスタイルで決めてください。


1
これは正確には当てはまりません。STLは、ほとんどの操作に明示的なBig-O要件を提供するという点で、他のほとんどのライブラリとは異なります。関数がO(log n)の動作を実現するために使用する実装に関係なく、2 * O(log n)と1 * O(log n)の間には保証された違いがあります。その違いがプラットフォームで重要かどうかは別の問題です。しかし、違いは常にそこにあります。
srm 2013

@srmでbig-O要件を定義しても、操作が絶対的にどれだけかかるかはわかりません。あなたが言う保証された違いは存在しません。
Mark Ransom

-2

map [key]-stlに分類させます。それはあなたの意図を最も効果的に伝えています。

ええ、十分公正です。

検索を行ってから挿入を行った場合、ミスが発生したときに2 x O(log N)を実行しています。これは、挿入が必要な場所ではなく、挿入する必要があるかどうかのみを示すためです(lower_boundが役立つ場合があります)。 。まっすぐに挿入して結果を確認するのが私のやり方です。


いいえ、エントリが存在する場合は、既存のエントリへの参照を返します。
クリスクムラー2008

2
この答えは-1です。クリスKが言ったように、map [key] = valueを使用すると既存のエントリが上書きされ、質問での必要に応じて「保存」されません。map [key]を使用して存在をテストすることはできません。キーが存在しない場合はデフォルトで作成されたオブジェクトを返し、それをキーのエントリとして作成します
netjeff

ポイントは、マップがすでに入力されているかどうかをテストし、そこにない場合にのみ追加/上書きすることです。map [key]を使用する場合、値は常にそこにあると想定しています。
srm 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.