web-dev-qa-db-ja.com

C ++でHashMapを使用する最良の方法は何ですか?

STLにはHashMap APIがあることは知っていますが、これに関する優れた例のある優れた完全なドキュメントは見つかりません。

良い例はありがたいです。

137
user855

標準ライブラリには、順序付きおよび順序なしマップ( std::map および std::unordered_map )コンテナーが含まれています。順序付けられたマップでは、要素はキー、挿入、およびアクセスでソートされます O(log n) 。通常、標準ライブラリは内部で red black trees を順序付きマップに使用します。しかし、これは実装の詳細にすぎません。順序付けられていないマップでは、挿入とアクセスはO(1)にあります。ハッシュテーブルの単なる別の名前です。

(順序付けされた)std::mapの例:

#include <map>
#include <iostream>
#include <cassert>

int main(int argc, char **argv)
{
  std::map<std::string, int> m;
  m["hello"] = 23;
  // check if key is present
  if (m.find("world") != m.end())
    std::cout << "map contains key world!\n";
  // retrieve
  std::cout << m["hello"] << '\n';
  std::map<std::string, int>::iterator i = m.find("hello");
  assert(i != m.end());
  std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
  return 0;
}

出力:

 23 
キー:hello値:23 

コンテナで注文する必要があり、O(log n)ランタイムで問題ない場合は、std::mapを使用します。

それ以外の場合、本当にハッシュテーブル(O(1)の挿入/アクセス)が必要な場合は、std::unordered_map APIに似たstd::mapを確認してください(たとえば、上の例では、mapを検索してunordered_mapに置き換えるだけです)。

unordered_mapコンテナは C++ 11標準 リビジョンで導入されました。したがって、コンパイラに応じて、C++ 11機能を有効にする必要があります(たとえば、GCC 4.8を使用する場合は、-std=c++11をCXXFLAGSに追加する必要があります)。

C++ 11リリースの前でも、GCCはunordered_mapをサポートしていました-名前空間std::tr1で。したがって、古いGCCコンパイラの場合、次のように使用することができます。

#include <tr1/unordered_map>

std::tr1::unordered_map<std::string, int> m;

また、ブーストの一部です。つまり、移植性を高めるために対応する boost-header を使用できます。

185
maxschlepzig

hash_mapは、標準化のためにunordered_mapと呼ばれるものの古い、標準化されていないバージョンです(元々はTR1で、C++ 11以降の標準に含まれています)。名前が示すように、std::mapとは主に順序付けられていない点で異なります。たとえば、begin()からend()へのマップを反復処理すると、キー順にアイテムが取得されます。1、しかし、unordered_mapbegin()からend()に繰り返し処理すると、アイテムは多かれ少なかれ任意の順序で取得されます。

unordered_mapは通常、一定の複雑さを持つことが期待されています。つまり、挿入、ルックアップなどには、通常、テーブル内のアイテムの数に関係なく、基本的に一定の時間がかかります。 std::mapの複雑さは、格納されているアイテムの数に対して対数的です。つまり、アイテムを挿入または取得する時間は長くなりますが、マップが大きくなると、かなりゆっくりになります。たとえば、100万アイテムの1つを検索するのに1マイクロ秒かかる場合、200万アイテムの1つを検索するのに約2マイクロ秒、400万アイテムの1つを3マイクロ秒、800万アイテムの1つを4マイクロ秒かかると予想できますアイテムなど.

実用的な観点から、それは実際には全体の話ではありません。本質的に、単純なハッシュテーブルのサイズは固定されています。汎用コンテナの可変サイズ要件にそれを適合させることは、いくぶん簡単ではありません。結果として、テーブルを(潜在的に)拡大する操作(挿入など)は、比較的遅い可能性があります(つまり、ほとんどはかなり高速ですが、定期的にはかなり遅くなります)。テーブルのサイズを変更できないルックアップは、一般的にはるかに高速です。結果として、ほとんどのハッシュベースのテーブルは、挿入数と比較して多くのルックアップを行うときに最高の状態になる傾向があります。大量のデータを挿入する場合、テーブルを1回繰り返して結果を取得します(たとえば、ファイル内の一意の単語の数をカウントする)可能性は、std::mapが同じくらい、そしておそらくさらに高速になることです(しかし繰り返しますが、計算の複雑さは異なるため、ファイル内の一意の単語の数にも依存します)。


1 マップを作成するときに、3番目のテンプレートパラメーターによって順序が定義される場所(デフォルトではstd::less<T>)。

26
Jerry Coffin

コンパイルエラーを生成するために必要なインクルードを省略しない、より完全で柔軟な例を次に示します。

#include <iostream>
#include <unordered_map>

class Hashtable {
    std::unordered_map<const void *, const void *> htmap;

public:
    void put(const void *key, const void *value) {
            htmap[key] = value;
    }

    const void *get(const void *key) {
            return htmap[key];
    }

};

int main() {
    Hashtable ht;
    ht.put("Bob", "Dylan");
    int one = 1;
    ht.put("one", &one);
    std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}

一致する値は機能しないため、ポインターとして事前に定義されていない限り、キーにはまだ特に有用ではありません! (ただし、通常はキーに文字列を使用するため、キーの宣言で「const void *」を「string」に置き換えることでこの問題を解決できます。)

22
Jerry Miller

std::unordered_mapがGCC stdlibc ++ 6.4でハッシュマップを使用するという証拠

https://stackoverflow.com/a/3578247/895245 で言及されましたが、次の回答では: C++のstd :: map内のデータ構造は? GCC stdlibc ++ 6.4の実装について、そのような証拠をさらに示しました。

  • クラスへのGDBステップデバッグ
  • パフォーマンス特性分析

その回答で説明されているパフォーマンス特性グラフのプレビューを次に示します。

enter image description here

unordered_mapでカスタムクラスとハッシュ関数を使用する方法

この答えはそれを釘付けにします: C++カスタムクラスタイプをキーとして使用するunordered_map

抜粋:平等:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

ハッシュ関数:

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}