既存のエントリを保持したいマップを想定しています。20%の確率で、挿入するエントリは新しいデータです。返されたイテレータを使用してstd :: map :: findを実行してからstd :: map :: insertを実行する利点はありますか?または、挿入を試みてから、レコードが挿入されたか、挿入されなかったかをイテレータが示すかどうかに基づいて動作する方が速いですか?
回答:
答えはどちらもしないことです。代わりに、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
}
この質問への答えは、マップに格納している値タイプを作成するのにどれだけ費用がかかるかにも依存します。
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ポインターを使用し(非常に安価に作成できます)、必要な場合にのみ、新しいデータ型が作成されます。
検索してから挿入を行うと、キーが見つからず、後で挿入を実行したときに、追加のコストがかかると思います。これは、本をアルファベット順に調べて本を見つけるのではなく、本をもう一度調べて、どこに挿入するかを確認するようなものです。つまり、キーをどのように処理するか、そしてキーが常に変化するかどうかです。今では、柔軟性があり、見つからない場合は、ログに記録して、例外を作成し、好きなことを行うことができます...
私はトップの答えで迷っています。
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回検索する必要があるため、まだマップにない要素の場合。そこにあるかどうかを確認し、もう一度新しいものを置く場所を見つけます。
lower_bound
、ありませんfind
。その結果、キーが見つからなかった場合は、イテレータが末尾ではなく挿入ポイントに返されました。その結果、それはより高速です。
効率が気になる場合は、hash_map <>をチェックしてください。
通常、map <>はバイナリツリーとして実装されます。必要に応じて、hash_mapの方が効率的です。
私はコメントを残すのに十分なポイントがないようですが、チェックされた答えは私に長い間巻き込まれているようです-挿入がとにかくイテレータを返すと考えるとき、返されたイテレータをそのまま使用できるのに、なぜlower_boundを検索するのですか?奇妙な。
std::map::value_type
オブジェクトを作成する必要があることを意味するため、受け入れられた回答はそれを回避します。
効率に関する答えは、STLの正確な実装によって異なります。確実に知る唯一の方法は、それを両方の方法でベンチマークすることです。大きな違いはないと思いますので、好みのスタイルで決めてください。
map [key]-stlに分類させます。それはあなたの意図を最も効果的に伝えています。
ええ、十分公正です。
検索を行ってから挿入を行った場合、ミスが発生したときに2 x O(log N)を実行しています。これは、挿入が必要な場所ではなく、挿入する必要があるかどうかのみを示すためです(lower_boundが役立つ場合があります)。 。まっすぐに挿入して結果を確認するのが私のやり方です。