web-dev-qa-db-ja.com

C ++ unordered_mapで[]演算子を効率的に使用する

まず、C++で[]演算子をルックアップ用のunordered_mapと組み合わせて使用​​すると、find()メソッドの呼び出しがラップされるか、[]演算子をfind()よりも早く使用するかを明確にできますか?

第二に、キーがunordered_mapにない場合に疑われる次のコードでは、map[key] = value行を使用して2番目のルックアップを実行し、そこに作成されたデフォルト値を置き換えます。 []キーが存在しない場合の演算子。

それは本当ですか、もしそうなら、私はいずれかの場合に1回だけ検索を実行する方法(おそらくポインタまたは何かを使用して)があります(おそらく値を配置する/値を読み取る場所のアドレスを保存することによって)それでも同じ機能を達成しますか?もしそうなら、これは明らかに効率の改善になります。

変更されたコードの抜粋を次に示します。

    int stored_val = map[key]; // first look up. Does this wrap ->find()??

    // return the corresponding value if we find the key in the map - ie != 0
    if (stored_val) return stored_val;

    // if not in map
    map[key] = value; 
       /* second (unnecessary?) look up here to find position for newly 
          added key entry */

   return value;
35
Darius

_operator[]_ は、デフォルトで構築された値のエントリが存在しない場合、エントリを挿入します。これは同等ですが、おそらく以下よりも効率的に実装されます。

_iterator iter = map.find(key);

if(iter == map.end())
{
    iter = map.insert(value_type(key, int())).first;
}

return *iter;
_

_operator[]_は、 find() および insert() を使用して手動で作業するよりも高速です。キーを再ハッシュします。

コード内で複数のルックアップを持つことを回避する1つの方法は、値への参照を取得することです。

_int &stored_val = map[key];

// return the corresponding value if we find the key in the map - ie != 0
if (stored_val) return stored_val;

// if not in map
stored_val = value;

return value;
_

値がマップに存在しない場合、_operator[]_はデフォルトで構築して挿入します。したがって、これは複数のルックアップを回避しますが、copy-またはmove-constructよりもdefault-construct + assignの方が遅いタイプで使用すると、実際には遅くなる可能性があります。

intを使用すると、デフォルトで0に安価に構築されるため、空を意味するマジックナンバーとして0を処理できる場合があります。これは、あなたの例の場合のようです。

そのようなマジックナンバーがない場合は、2つのオプションがあります。使用する値は、値を計算するのにどれだけ費用がかかるかによって異なります。

まず、キーのハッシュは安価ですが、値の計算は高価な場合、find()が最適なオプションです。これは2回ハッシュしますが、必要な場合にのみ値を計算します:

_iterator iter = map.find(key);

// return the corresponding value if we find the key in the map
if(iter != map.end()) return *iter;

// if not in map
map.insert(value_type(key, value));

return value;
_

ただし、すでに値を取得している場合は、非常に効率的に実行できます。おそらく、上記のように参照+マジックナンバーを使用するよりも少し効率的です。

_pair<iterator,bool> iter = map.insert(value_type(key, value));
return *iter.first;
_

map.insert(value_type)によって返されるブールがtrueの場合、アイテムは挿入されました。それ以外の場合は、すでに存在し、変更は行われていません。反復子は、マップに挿入された値または既存の値へのポイントを返しました。簡単な例では、これが最良のオプションかもしれません。

45
Cory Nelson

要素が存在するかどうかを確認できますおよび新しい要素が存在しない場合は、特別なinsert関数を使用して挿入しますブール値が実際に値が挿入されたかどうかを示すpair<iterator, bool>たとえば、コード here

  unordered_map<char, int> mymap;
  pair<unordered_map<char,int>::iterator,bool> ret;

  // first insert function version (single parameter):;
  mymap.insert ( pair<char,int>('z',200) );
  ret=mymap.insert (pair<char,int>('z',500) ); 
  if (ret.second==false)
  {
    cout << "element 'z' already existed";
    cout << " with a value of " << ret.first->second << endl;
  }

このコードは、ペア[<'z',200>]が存在しない場合、マップに挿入します。返されたペアの2番目の要素の値がtrueの場合、挿入されたイテレータを返します。ペアの2番目の要素がfalseの場合、要素が実際にあったイテレータを返します。

10
Diego Sevilla

まず、C++でルックアップにunordered_mapと組み合わせて[]演算子を使用すると、Find()メソッドの呼び出しがラップされるのか、それとも[]演算子をFind()よりも速く使用するのかを明確にできますか?

そのためのルールはありません。 _[]_の実装はfind()を使用するか、それ自体でルックアップを実行するか、find()によって内部的に使用されるプライベートメソッドにルックアップを委任することができます。

また、どちらが速いかについての保証もありません。 find()は、イテレータの構築と返送にオーバーヘッドを伴いますが、この場合新しい値を挿入するため、キーが存在しない場合は_[]_はおそらく遅くなります。

(...)どのような場合でも1回だけ検索を実行できる方法(おそらくポインターまたは何かを使用する方法)があります(...)

キーがマップにない場合、_[]_は新しいデフォルトの構築値を挿入し、参照を返します。したがって、その参照を保存して、2番目のルックアップを保存できます。

_int& stored_val = map[key];  // Note the reference

if (stored_val) return stored_val;

// Use the reference to save a second lookup.
stored_val = value; 

return value;
_
2
Ferdinand Beyer