web-dev-qa-db-ja.com

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

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

84
Superpolock

答えはどちらもしません。代わりに Effective STL by Scott Meyers の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
}
138
luke

この質問に対する答えは、マップに保存している値の型を作成するのにどれだけ費用がかかるかにも依存します。

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などの値型の場合、上記はコンパイラの最適化がない場合に挿入が続く検索よりも効率的です。前述のように、これは、マップ全体の検索が1回だけ行われるためです。

ただし、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ポインター(非常に安価に構築)が使用され、必要な場合にのみ新しいデータ型が構築されます。

11
Richard Corden

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

だから..個人的な好みにダウン。私は常に挿入してから必要に応じて更新しようとしますが、返されるペアの処理を好まない人もいます。

8
gbjbaanb

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

5
PiNoYBoY82

私は一番上の答えに迷っています。

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回検索する必要があるため、まだマップにない要素については。一度それがそこにあるかどうかを確認し、新しいものを置く場所を見つけます。

3
gman

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

1
Stonky

効率が心配な場合は、 hash_map <> を確認してください。

通常、map <>はバイナリツリーとして実装されます。ニーズによっては、hash_mapの方が効率的な場合があります。

1
Adam Tegen

効率に関する回答は、STLの正確な実装に依存します。確実に知る唯一の方法は、両方の方法でベンチマークすることです。違いはそれほど大きくないと思われるので、好みのスタイルに基づいて決定してください。

0
Mark Ransom