こんにちは!のりです。
今回はHash(ハッシュ)についてのお話です。
Hashのメリットを説明するために、まずはenumやstringの使用例について簡単に書いてみたいと思います。例としてステージの種類を定義する時、一般的には enum や string を使うことがあると思います。
---【enumの使用例】-----------------------------
・ステージ名をenumで定義して変数に設定したり比較したりして使用する方法です。よく見かけるコードだと思います。
enum Stage
{
STAGE_FOREST = 0, // 森 (0)
STAGE_CASTLE, // 城 (1)
STAGE_CAVE, // 洞窟 (2)
};
Stage stage = STAGE_FOREST;
// ...
stage = STAGE_CASTLE;
// ...
if(stage == STAGE_CASTLE)
{
// ...
}
【メリット】
・STAGE_FORESTなどは定数なので処理速度が速い!
【デメリット】
・ステージ追加の度にSTAGE_XXXを手動で追加するのが面倒。
・Excelなど別のツールでenumを使用する場合、Excel側でも同じenumテーブルを参照しないと数値がずれる。よって、C++のソースとExcel側両方のenumを更新しないといけないため、面倒だしバグの原因になりやすい。
---【stringの使用例】-----------------------------
・ステージ名をstring(文字列)で設定したり比較したりする方法です。enumテーブルのような追加作業がいらないためかなり楽に実装できます。しかし、文字列の比較は非常に処理速度が遅いためゲーム会社ではあまり使用されません。デバッグ用のコードに使用するのは良いですが、製品コードに使用するとバグ扱いのレベルです。
std::string stage("STAGE_FOREST");
// ...
stage = "STAGE_CASTLE";
// ...
if(stage == std::string("STAGE_CASTLE"))
{
// ...
}
【メリット】
・enumテーブルのような追加作業がいらないため実装が楽!
・Excelなど別のツールで使用する場合も同じenumテーブルなどを参照する必要がないため、やはり実装が楽!
【デメリット】
・string比較の処理速度が非常に遅い!製品では使用できないレベル。
(もちろん、数回程度のstring比較は問題ありませんが、1,000個以上のリソース文字列の比較に使用するなどstring比較をする数が増えほどロード時間や処理負荷が倍々で増えてしまいます。)
---【Hashについて】-----------------------------
さて、enumは処理速度が速いが追加などの管理が面倒。stringは処理速度が遅いが管理するものがないので楽。といった一長一短の状況ですが、ゲーム業界では基本的にstring比較すると処理負荷やロード時間の増加が無視できないのでenum一択となっていることが多いです。
しかし、そんな中 C++11 によって追加された constexpr キーワードによって実装可能となった Hash という仕組みが現れました!C++11 では std::hash が用意されていますがこの std::hash はコンパイラの種類によってハッシュ値が変わってしまうので、ExcelとC#など別のツールと組み合わせて使用するためには独自でHashクラスを実装する必要があります。
Hash stage("STAGE_FOREST");
// ...
stage = Hash("STAGE_CASTLE");
// ...
if(stage.GetDigest() == HASH_DIGEST("STAGE_CASTLE"))
{
// ...
}
switch(stage.GetDigest())
{
case HASH_DIGEST("STAGE_FOREST"):
// ...
break;
case HASH_DIGEST("STAGE_CASTLE"):
// ...
break;
case HASH_DIGEST("STAGE_CAVE"):
// ...
break;
default:
break;
}
上記の「Hash」は独自のクラスですが、コード上はstringのように文字列を扱うことができます。さらにstringでは不可能なswitch文のcaseに文字列を書くような使い方も可能です。これは、HASH_DIGEST("STAGE_FOREST")が内部的には文字列ではなく定数になっていることを意味します。
ただし、Hashに詳しい方ならすぐに思いつくと思いますが、Hashには衝突のリスクがあります。異なる文字列が同じ定数値になってしまうことがあるということです。これは大問題ではあるのですが、今回紹介するCRC32アルゴリズムで生成されるハッシュ値が衝突する確率は約1/43億となっています。仮にゲーム開発で衝突によるバグが発生してもハッシュ値は固定なので再現性があり、その部分だけをHash比較+文字列比較にすれば回避は可能です。
ということで、Hashには以下のようなメリットとデメリットがあります。
【メリット】
・enumテーブルのようなものが不要で文字列で管理できるので楽!
・文字列はコンパイル時に定数に変換されるのでenumと同様に処理速度が速い!
【デメリット】
・約1/43億という低確率ではあるが、ハッシュ値の衝突リスクがある。
・どうしても衝突してはいけないシステムにおいての使用はおすすめできない。衝突によるバグが発生してしまったら、その部分だけ文字列比較にするなどの回避策が必要。
ということで、衝突リスクがあるとは言え確率の低さや十分なデバッグを行うことを考えれば、文字列管理が楽で高速という十分なメリットがあるHashは魅力的だと思います。実際にここ最近はHashを使用したゲーム開発が増えていて私もHashはかなり便利だと実感しています。
前置きが長くなってしまいましたが、以下にて「Hashクラス」の実装方法について説明していきたいと思います。
目次
なぜ、std::hash ではだめなのか?
C++11 では、std::hash というstructが用意されています。この std::hash にも文字列を指定することができハッシュ値(size_t)へと変換できます。std::hashに文字列を指定する場合、const char* はサポート外ということでポインタのハッシュ値となってしまいます。
よって、std::hashに文字列を指定する場合はstd::stringを引数に渡すことになります。
std::string stage_name("STAGE_FOREST");
size_t digest_1 = Hash(stage_name.c_str()).GetDigest();
size_t digest_2 = std::hash{}(stage_name);
std::cout << "digest1 = " << digest_1 << std::endl;
std::cout << "digest2 = " << digest_2 << std::endl;
上記のコードにて独自Hashクラス(digest1)とstd::hash(digest2)で「STAGE_FOREST」の文字列をハッシュ値に変換してみます。
【gcc】
digest1 = 897775596
digest2 = 14277071941410348173
【clang】
digest1 = 897775596
digest2 = 6501148428165968206
試しにgccコンパイラとclangコンパイラで結果を比較してみました。独自Hashクラスは89775596という同じ値になりましたが、std::hashは異なる値になってしまいました。一つのexeで完結するプログラムであれば問題ありませんが、別のツールでC#を使ってハッシュを生成したりすると異なるハッシュ値が生成されてしまう可能性があるため、少なくともゲーム開発のようなゲームのメインコード以外に複数のツールでデータをコンバートするような開発環境でstd::hashはおすすめできません。
ということで、以下で独自Hashクラスの実装について書いていきたいと思います。
・crc32_table.h
・crc32_table.cpp
・hash.h
・hash.cpp
の4つのファイルを実装すれば独自Hashクラスを使用することができます。
crc32_table.h
CRC32とは、CRC (巡回冗長検査、Cyclic Redundancy Check)というデータ転送などで使用する受信データの誤りを検知するアルゴリズムの一種で、その中でもdigest(ダイジェスト値、要約値)が32ビットになるものです。
今回は文字列(const char*)をCRC32変換したダイジェスト値をハッシュ値(uint32_t)として使用しています。CRC32によるハッシュ値変換のプログラムはいろんなウェブページや書籍に載っており特別なものではありませんのでアルゴリズムの詳細を知りたい方はCRCなどでググっていただければと思います。
#pragma once
#define CRC32_TABLE_SIZE (256) // CRC32テーブルの要素数
#define USE_CREATE_CRC32_TABLE_FUNCTION (0) // 1: CRC32テーブル作成関数を使用する。 0: 使用しない。
#if USE_CREATE_CRC32_TABLE_FUNCTION
//! CRC32テーブルを作成します。
void CreateCrc32Table();
//! CRC32テーブルをログに出力します。
void PrintCrc32Table();
#endif
// 生成済みのCRC32テーブル
static constexpr uint32_t s_crc32_table[ CRC32_TABLE_SIZE ] =
{
0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9,
0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011,
0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD,
0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5,
0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81,
0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49,
0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D,
0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE,
0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA,
0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE,
0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02,
0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066,
0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E,
0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692,
0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6,
0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A,
0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686,
0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A,
0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637,
0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F,
0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47,
0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B,
0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623,
0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7,
0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F,
0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B,
0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F,
0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,
0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C,
0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8,
0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24,
0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30,
0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088,
0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654,
0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0,
0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C,
0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0,
0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C,
0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668,
0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4,
};
crc32_table.h では、既にCRC32アルゴリズムで生成した s_crc32_table という静的なテーブルを用意しています。このテーブルは CreateCrc32Table() で生成することができますが、プログラム実行時は s_crc32_table を使用するので基本的に必要ありません。自分でテーブルを生成してみたい方は USE_CREATE_CRC32_TABLE_FUNCTION を (1) に変更して、PrintCrc32Table() を実行していただければログに s_crc32_table の中身と同じものが出力されます。
crc32_table.cpp
crc32_table.cpp では、crc32_table.h で宣言していた CreateCrc32Table() と PrintCrc32Table() の実装が書かれています。ハッシュテーブルを生成する以外の機能は無いので、このコードは基本的に必要ありませんが解析防止などのためにアルゴリズムを変えたCRC32テーブルを生成したい場合はこのコードを変えて生成したテーブルを使用すると良いと思います。
#include "crc32_table.h"
#include <sstream>
#include <iomanip>
#if USE_CREATE_CRC32_TABLE_FUNCTION
static uint32_t g_crc32_table[ CRC32_TABLE_SIZE ] = {0};
void CreateCrc32Table()
{
for (uint32_t i = 0; i < CRC32_TABLE_SIZE; i++)
{
uint32_t c = i << 24;
for (int j = 0; j < 8; j++)
{
c = (c << 1) ^ ((c & 0x80000000) ? 0x04c11db7 : 0);
}
g_crc32_table[i] = c;
}
}
void PrintCrc32Table()
{
std::string str = "";
for (uint32_t i = 0; i < CRC32_TABLE_SIZE; i++)
{
std::stringstream stream;
stream << std::setfill ('0') << std::setw(sizeof(uint32_t)*2) << std::hex << g_crc32_table[i];
std::string s = stream.str();
transform (s.begin(), s.end(), s.begin(), toupper);
str += "0x";
str += s;
str += ", ";
if(i%4 == 3) str += "\n";
if(i%64 == 63) str += "\n";
}
std::cout << str << std::endl;
}
#endif
hash.h
本題であるHashクラスのヘッダです。
#pragma once
#define ENABLE_HASH_DEBUG (1) // 1: Hashのデバッグ機能を有効, 0: 無効
class Hash
{
public:
Hash(const char* str);
//! ハッシュ値を取得
uint32_t GetDigest() const { return m_digest; }
//! 文字列からハッシュ値を作成して取得
static constexpr uint32_t GetDigest(const char* str, const size_t length);
private:
uint32_t m_digest = 0; // ハッシュ値
#if ENABLE_HASH_DEBUG
public:
//! (デバッグ用) 文字列を取得
const char* GetDebugStr() const { return m_debug_str.c_str(); }
private:
std::string m_debug_str = ""; // (デバッグ用) 文字列
#endif
};
// 文字列をハッシュ値に変換する。
// 使用例: HASH_DIGEST("STAGE_FOREST")
// sizeofはnull終端を含み文字数+1されるので-1して文字数のみにしておく。
#define HASH_DIGEST(str) Hash::GetDigest(str, (sizeof(str)-1))
static constexpr uint32_t GetDigest(str, length) がハッシュ値(ダイジェスト値)を生成する関数です。staticなので Hash::GetDigest() のような形で使用できます。constexprはコンパイル時に計算されてプログラム起動時はconstとして扱われるというC++11からの追加されたキーワードです。これにより、GetDigest()の計算がビルド時に行われその戻り値がconstの定数としてswitch文などに使用できるといった挙動が可能となります。
switch文やifの比較に使用するには、HASH_DIGEST() というdefineを用意してあり、中身はHash::GetDigest()となっています。
プログラム実行中は、Hash(str).GetDigest()として使用します。
デバッグ用として、ENABLE_HASH_DEBUGを有効にするとm_debug_strとして文字列を保存してデバッグ表示などに使用できます。製品などのリリース時にはENABLE_HASH_DEBUGは無効にしてuint32_tのダイジェスト値のみの利用となります。
hash.cpp
Hashクラスの実装コードです。
#include "hash.h"
#include "crc32_table.h"
Hash::Hash(const char* str)
{
m_digest = GetDigest(str, std::string(str).length());
#if ENABLE_HASH_DEBUG
m_debug_str = str;
#endif
}
constexpr uint32_t Hash::GetDigest(const char* str, const size_t length)
{
uint32_t digest = 0xffffffff;
for (size_t i = 0; i < length; i++)
{
digest = (digest << 8) ^ s_crc32_table[((digest >> 24) ^ str[i]) & 0xff];
}
return digest;
}
コンストラクタ(Hash::Hash)では、GetDigest()でダイジェスト値を生成してデバッグ文字列を保存するだけです。
GetDigest()では文字列 str と文字列の長さ length からダイジェスト値を求めています。ここの計算式はCRC32のアルゴリズムで「CRC」でググったりするとすぐに出てくる計算式なので詳細はそちらをご参照下さい。多項式というものです。
constexpr は hash.h でも触れましたが、コンパイル時に計算が実行され起動時はconstと扱われるというキーワードです。
使用例
実際にmain()で使用してみます。
#include
#include "crc32_table.h" // CRC32テーブルを作成する場合のみinclude
#include "hash.h"
int main()
{
#if USE_CREATE_CRC32_TABLE_FUNCTION
// CRC32テーブルを作成する。
CreateCrc32Table();
// CRC32テーブルをログに出力する。
PrintCrc32Table();
#endif
Hash stage = Hash("STAGE_FOREST");
#if ENABLE_HASH_DEBUG
std::cout << stage.GetDebugStr() << ", " << stage.GetDigest() << std::endl;
#endif
stage = Hash("STAGE_CASTLE");
if(stage.GetDigest() == HASH_DIGEST("STAGE_CASTLE"))
{
#if ENABLE_HASH_DEBUG
std::cout << stage.GetDebugStr() << ", " << stage.GetDigest() << std::endl;
#endif
}
stage = Hash("STAGE_CAVE");
switch(stage.GetDigest())
{
case HASH_DIGEST("STAGE_FOREST"):
case HASH_DIGEST("STAGE_CASTLE"):
case HASH_DIGEST("STAGE_CAVE"):
#if ENABLE_HASH_DEBUG
std::cout << stage.GetDebugStr() << ", " << stage.GetDigest() << std::endl;
#endif
break;
default:
break;
}
}
最初に、CreateCrc32Table(); でCRC32テーブルを生成して PrintCrc32Table(); で値をログに出力してみます。出力結果が以下のようになり、crc32_table.h の s_crc32_table と同じものが出力されていることが確認できます。
Hashは、Hash stage = Hash("STAGE_FOREST"); か Hash stage("STAGE_FOREST"); といった書き方が可能で、stage.GetDigest() でハッシュ値(ダイジェスト値)、stage.GetDebugStr() でデバッグ用の文字列を取得することができます。if文やswitch文で比較する時は、HASH_DIGEST("STAGE_FOREST")のdefineでconstexprコンパイルされるので定数となり文字列比較に比べて高速です。
0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9,
0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011,
0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD,
0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5,
0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81,
0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49,
0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D,
0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE,
0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA,
0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE,
0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02,
0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066,
0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E,
0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692,
0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6,
0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A,
0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686,
0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A,
0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637,
0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F,
0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47,
0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B,
0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623,
0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7,
0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F,
0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B,
0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F,
0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,
0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C,
0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8,
0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24,
0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30,
0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088,
0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654,
0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0,
0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C,
0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0,
0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C,
0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668,
0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4,
STAGE_FOREST, 897775596
STAGE_CASTLE, 2486442583
STAGE_CAVE, 1797693044
CRC32テーブルの後に、STAGE_FOREST, STAGE_CASTLE, STAGE_CAVE の文字列とハッシュ値がログに出力され、上記のプログラムコードが正常に動作していることを確認できます。
あとがき
ということで、Hashの実装と使用例についてでした。ハッシュの衝突リスクについては気になるところですが、私自身5年くらいHashを実際に使用してまだ衝突に遭遇したことはありません。少なくともゲーム開発においては、衝突リスクが怖いのでenumやstringを使うべきという方は聞いたことがなく、ほとんどの方が気にせずハッシュ便利!って感じで使っている気がします。(笑) まあ、1/43億の確率なので気にしても仕方がありません。銀行や役所などのシステムに使用するのはさすがにどうかと思いますが、ゲーム開発であればメリットの方が明らかに大きいので是非おすすめしたい機能です!
以上、Hash (ハッシュ) を活用しよう!enumやstringのいいとこ取り!でした。