• ベストアンサー

ハッシュ関数

ハッシュ関数について悩んでいます. ハッシュ関数の例として以下Wikiの引用です http://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0 unsigned int hash(char *s) { unsigned int h = 0; for (;*s != '\0'; s++) { h = h * 137 + *s; } return h % 1987; } このハッシュ関数において, 「hの更新計算で用いる137という数は、2(7乗)+2(3乗)+2(0乗) というものであり、乗法した後にも2進数表現の下位の桁にも 情報が残るようになっている。そのため前の方の文字の情報が 桁あふれによって失われることはない。」 と記述されているのですが,私が考えてしまうのは以下の通りです. 「最終的にハッシュ値は合計を1987で割った余りになるのだから, 下位の桁(1~1987)に情報を残さなければならない. それぞれのsの上位から下位までの情報を合計の数値の下位に 残すのであれば,137は掛けるのではなく割るべき.」 もちろん私の考え方が間違っているのはわかりますが, なぜ137を掛けるのかを理解できません. どなたかわかりやすくご教授いただけたら幸いです.

質問者が選んだベストアンサー

  • ベストアンサー
  • mtaka2
  • ベストアンサー率73% (867/1179)
回答No.2

> 「最終的にハッシュ値は合計を1987で割った余りになるのだから, > 下位の桁(1~1987)に情報を残さなければならない. ここ、間違いです。 余りを取る元の数において、「1と2の区別」ができるのと同じように、 「1988と1989の区別」ができます。 「1987で割った余り」を取る元の数は1987を超えてても問題ありません。 問題は、元の数が「unsigned int」の範囲に収まるかどうです。 つまり、 > 乗法した後にも2進数表現の下位の桁にも情報が残るように の「下位の桁」とは、「unsigned int の表現能力である32bit」を指すのです。 たとえば、計算式が「h=h*256+*s」だったとします。 これでもし、元の文字列が"ABCDEFGH"だったとしたら、 ループ処理後最終的なhの値は、その桁数に制限が無かったとしたら 「h=0x4142434445464748」になるはずです。(Aのアスキーコード=0x41~Hのアスキーコード=0x48) ところが、hは32bit変数ですから、この64bitな数の上位32bitは切り捨てられて、 「h=0x45464748」になります。ここから求まるハッシュ値は1338(0x45464748を1987で割った余り)になりますが、 このハッシュ値は、元になるhに文字列の最後の4文字の情報しか反映されてません。 つまり、このようなハッシュ関数では、最後の4文字だけがハッシュ値に反映され、それより前の部分の文字が異なっていても、同じハッシュ値になってしまうわけです。 これが「前の方の文字の情報が桁あふれによって失われ」た状態です。 そうならないようにするためには、かける数を2進数で見て最下位が1だったら大丈夫です。 そういう数は137以外にもいろいろありますから、 その中で「137」を選んだことには論理的理由はそれほどありません。 強いて言えば「統計的・経験的にみてハッシュ値の散らばり具合が良い」数だったから、ですね。

msi8110
質問者

お礼

間違いを明確に指摘していただいたのでとてもわかりやすかったです. 1987の範囲に情報の重みを置かないといけないと考えていたのですが, 「余り」という概念の使い方を誤っていたようです. ありがとうございます.

その他の回答 (1)

  • shred
  • ベストアンサー率35% (25/70)
回答No.1

137で割っていくと情報が全然残りません。 文字列の最後の2つが同じ文字であった場合ハッシュ値が同じになります。ハッシュ関数としては使い物になりません。 今回12bitのハッシュ値を返すハッシュ関数を作りたい。 対象とする文字列は大きすぎるので文字単位で計算を行う。 文字は8bitである。 12bit以上になるように拡張処理を行う。 137 == 2<<7 + 2<<3 + 2<<0 です。 シフト操作でイメージしてください。 そうすればわかるはずです。

msi8110
質問者

お礼

前提条件を明示していただいて,頭が整理されました. 確かに割ると使いものにならなくなりますね. そして掛けると,intの範囲に情報が反映されます. 大変参考になりました.ありがとうございます.

関連するQ&A

  • 2のn乗+1

    nビットのハッシュ関数の出力は0から2のn乗の、合計すると2のn乗+1のパターンがあるってことで合ってますか? それとも0から2のn乗-1で、合計2のn乗パターンですか? もしくは0はなくて1から2のn乗までですか? 実際の出力をみると0000……FA16みたいなケースもあるので、そうすると全部0という出力も有り得るのですか? また最大値はn+1ビットも有り得るのですか?最大桁が1で残りが全て0とすると、2のn乗は2進数だと最大桁に1が来て、残りが全部0ですがその0の数は、n個になると思いますが、そうすると何々ビットのハッシュ関数といいつつ、最大値でみるとビット数は2のn乗+1となりますが、認識正しいでしょうか?

  • ハッシュのハッシュのソート

    rubyでハッシュのソート方法についてはいくつか情報のサイトを見つけられました。 ですが今やりたいのは、ハッシュのハッシュのソートなのですが、うまいやり方がわかりませんでした。 具体的には、 h1 = {"user1"=>{"a"=>10, "b"=>20, "c"=>30"}, "user2"=>{"d"=>5, "e"=>8}, "user3"=>{"f"=>10, "g"=>5, "h"=>10} } というようなハッシュのハッシュを想定しています。ユーザごとに案件ごとの必要工数(時間)をハッシュとして持たせ、全工数が多いユーザ順にソートしたいのです。 上記の場合だと、 {"user1"=>{"a"=>10, "b"=>20, "c"=>30"}, "user3"=>{"f"=>10, "g"=>5, "h"=>10}, "user2"=>{"d"=>5, "e"=>8} } というようにソートしたいのですが、何かやり方がありましたらご教授いただけますでしょうか。

    • ベストアンサー
    • Ruby
  • ハッシュ(オープンアドレス法) C言語の課題

    努力はしてみたのですが、C言語の課題ができません。教えていただけないでしょうか。 問:名前と年齢を入力し、名前をキーとしてハッシュ(オープンアドレス法)に登録する。'-'が入力されると登録を終了し、次に入力された名前をハッシュ法で検索し、あればその人のデータをハッシュから削除する、その後、ハッシュ表の内容を出力するプログラムを作成せよ。ただしハッシュ表の大きさは5とする。 例 koizumi  入力 1     入力 fukuda  入力 2  入力 aso 入力 3     入力 -     入力 koizumi  入力 fukuda(2) 出力 aso(3) 出力 ハッシュ関数は int hash(char *name) { int ret=0; while (*name)ret += *name++; return ret%5; } 再ハッシュ関数は int rehash(int h) { return (h+1)%5; } を使おうと考えています。 内容を理解できないと困るので簡単なプログラムをお願いします。 よろしくお願いします。

  • プログラミングハッシュ値についての質問

    #ifndef _SHA1_H_ #define _SHA1_H_ //#include <stdint.h> /* * If you do not have the ISO standard stdint.h header file, then you * must typdef the following: * name meaning * uint32_t unsigned 32 bit integer * uint8_t unsigned 8 bit integer (i.e., unsigned char) * int_least16_t integer of >= 16 bits * */ typedef unsigned int uint32_t; typedef unsigned char uint8_t; typedef short int_least16_t; #ifndef _SHA_enum_ #define _SHA_enum_ enum { shaSuccess = 0, shaNull, /* Null pointer parameter */ shaInputTooLong, /* input data too long */ shaStateError /* called Input after Result */ }; #endif #define SHA1HashSize 20 /* * This structure will hold context information for the SHA-1 * hashing operation */ typedef struct SHA1Context { uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ uint32_t Length_Low; /* Message length in bits */ uint32_t Length_High; /* Message length in bits */ /* Index into message block array */ int_least16_t Message_Block_Index; uint8_t Message_Block[64]; /* 512-bit message blocks */ int Computed; /* Is the digest computed? */ int Corrupted; /* Is the message digest corrupted? */ } SHA1Context; /* * Function Prototypes */ int SHA1Reset( SHA1Context *); int SHA1Input( SHA1Context *, const uint8_t *, unsigned int); int SHA1Result( SHA1Context *, uint8_t Message_Digest[SHA1HashSize]); #endif /* のプログラムを160ビットのハッシュ値を今出力しているのですが16ビットだけを出力するようにしたいです。[SHA1HashSize/4]のところを直せば16ビットだけ出力できるらしいのですが、今のところ書き変えてもそれらしい答えが出てきませんどなたかご教授ください

  • あらゆる種類の情報をハッシュで一定量に揃えれるなら

    0バイトの情報(情報が無いという情報)でもデータセンター1棟丸ごとの情報でも、もし仮にハッシュ関数を通すことができれば、一定のビット数で要約ができるのなら(他の情報と区別がつけられるのなら)、情報の表す意味と、その情報の特定性は、切り離す方向で考えたほうがいいのでしょうか。 例1 プログラムの作成時、関数名を決めたいが文字数制限があってうまく決められない→ 自分で充分に満足するような関数名をまずメモ帳にでも書いてみて、プログラムソース上はそのハッシュ値で関数名を決めてしまう。(当然、読みづらいソースになるので、HTMLのブラウザのような専用ビューワやエディターで閲覧編集する) 例2 あるWebサービスである名前でIDを取りたいが既に他人に取られていて自分のものとして使えない→諦めてハッシュをIDとする(それでも取られていたら変更回数として0でも末尾につけてまたハッシュする) そのWebサービスで取りたかったID名との対応表みたいのもまたネットに置いておく(オンラインブックマークにコメント記入するなど)

  • 関数先にて配列の長さを取得する方法

    関数にて、引数にて渡された配列(ポインタ)から配列数を調べる方法はないでしょうか? #include <stdio.h> void test(unsigned int *p){ printf ("%d\n", sizeof(*p)/sizeof(p[0])); //結果 1 } int main(){ unsigned int hoge[] = {0x30, 0x31,0x32}; test(hoge); printf("%d", sizeof(hoge)/sizeof(hoge[0])); //結果3 return 1; } 上記コードの関数testの*pから配列の数3が抜き取りたいと思っております。 よろしくお願いします。

  • セルを分けた合計で0を非表示にしたい

    一桁ごとにセルがわかれた数を合計表示する際、画像のような関数を使いました。 合計の一番先頭のセルには、 =int((e4+e5)/10)) と入れてみました。 e4+e5が10を越える場合は正しく表示できますが、10未満の場合は画像のように0が表示されてしまいます。 どのようにしたら0がなくなるのか、ご教示お願い致します。

  • strtol関数をmalloc()関数を使用して次のソースプログラムを

    strtol関数をmalloc()関数を使用して次のソースプログラムを修正しなさいを言われました。 どなたか詳しい方よろしくお願いします。 #include <limits.h> #include <ctype.h> #include <errno.h> #include <stdio.h> int _space_sign(const char *s, const char **endptr); int _space_sign(const char *s, const char **endptr) { int sign ; while (isspace((unsigned char)*s)) ++s; sign = 0; switch (*s) { case '-': sign = -1; // fall through case '+': ++s; break; } *endptr = s; return sign; } long int strtolong(const char * s, char ** endptr, int base) { int c; int sign = _space_sign(s, (const char**)&s); long result; if (s[0] == '0') { ++s; if ((s[1] | 0x20) == 'x') { if (base == 0 || base == 16) { ++s; base = 16; } } else if (base == 0) base = 8; } else if (base == 0) base = 10; result = 0; for (; c = tolower((unsigned char)*s), isdigit(c) || ('a' <= c && c <= 'v'); s++) { int d ; if( isdigit(c) ) d= c - '0' ; else d = c - 'a' + 10; if (d >= base) break; if (result > (LONG_MAX - d - sign) / base) { errno = ERANGE; result = sign ? LONG_MIN : LONG_MAX; } else { result = result * base + d; } } if (endptr != NULL) *endptr = (char*)s; if (sign != 0) result = -result; return result; } int main(void) { char s[128], *e; long n; int base; printf("何進数で変換しますか。"); scanf("%d", &base); printf("変換する数値を入力してください。"); scanf("%s", s); n = strtolong(s, &e, base); if (errno != ERANGE) { printf("変換数値=%ld\n", n); if (*e != '\0') { printf("変換不可能部分=%s\n", e); printf("%d文字目の\'%c\'が変換不可\n", e-s+1, *e); } } else if (n == LONG_MAX) printf("long値で表現できる値を上回りました。\n"); else if (n == LONG_MIN) printf("long値で表現できる値を下回りました。\n"); return 0; }

  • GlobalAllocの変数を関数に引き渡したい

    あるプログラムでGlobalAllocで指定バイト数を割り当てた変数を別の関数に引き渡したいのですが、やりかたが分からず困っております。 手元にある本はGlobalAllocについてすら書いておらず… 以下のようなプログラムになります。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー int math( int a, int b, unsigned short c ){ // 関数中身の演算に意味はない unsigned short sum; for( int i=a; i<b; i++ ) sum += c[i]; return sum; } int main(){ int Depth = 6; int sum; unsigned short *mem = GlobalAlloc( GPTR, Depth*sizeof(unsigned short) ); sum = math( 1, 3, mem ); // これな風にmemを値を引数として渡したい return 0; } ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー お分かりになる方がおられましたら、ご回答いただけると幸いです。 宜しくお願い致します。

  • c言語 ハッシュ表

    下のハッシュ表のプログラムについて質問です.関数enterを二回呼び出して,valueの値を変更して表示させても値が更新されません.値が更新せれるにはどうすればよいのでしょうか? #include <stdio.h> #include <stdlib.h> #include <string.h> #define HASH_SIZE 997 /* ハッシュ表の内部配列のサイズ */ #define HASH_RADIX 97 /* ハッシュ関数用の基数 */ /* ハッシュ表内の連結リストに含まれるノードの構造体 */ struct hash_node { /* ハッシュ表内の連結リストのノード */ char *key; /* キー */ int value; /* キーに対応する値 */ int id; /* キーに付与された通し番号 */ struct hash_node *next; /* 次のノードへのポインタ */ }; typedef struct hash_node HashNode; typedef HashNode *HashNodePtr; /* ハッシュ表の構造体 */ struct hashtable { HashNodePtr *heads; /* 内部配列 */ int serial_id; /* 通し番号管理用の変数 */ int size; /* 内部配列のサイズ */ }; typedef struct hashtable HashTable; typedef HashTable *HashTablePtr; /* 文字列 s のハッシュ値を計算する */ unsigned int hash(char *s) { unsigned int v; v = 0; while (*s != '\0') { v = v * HASH_RADIX + *s; s++; } return v; } /* ハッシュ表を一つ生成し,そのポインタを返す */ HashTablePtr create_hashtable() { HashTablePtr t = NULL; int i; t = malloc(sizeof(HashTable)); t->serial_id = 0; t->size = HASH_SIZE; t->heads = malloc(sizeof(HashNodePtr) * t->size); /* 各連結リストの先頭要素へのポインタは必ず NULL に初期化する */ for (i = 0; i < t->size; i++) { t->heads[i] = NULL; } return t; } /* 指定したポインタ変数に NULL を代入して終わるためのマクロ */ #define delete_hashtable(t) \ (delete_hashtable0(t),t=NULL) /* 実質的な削除作業を行う関数(free 後の NULL 代入は省略)*/ void delete_hashtable0(HashTablePtr t) { HashNodePtr n = NULL, m = NULL; int i; /* 各連結リストの領域を解放 */ for (i = 0; i < t->size; i++) { n = t->heads[i]; while (n != NULL) { m = n; n = n->next; free(m); } } /* 最後に連結リストの先頭ポインタの領域を解放 */ free(t->heads); free(t); } /* ハッシュ表 t に登録されているキーと値のペアの数を返す */ int get_cardinality(HashTablePtr t) { return t->serial_id; } /* ハッシュ表 t にてキー key に対応する値を調べる */ int lookup(HashTablePtr t, char *key) { HashNodePtr n = NULL; int index; /* ハッシュ表の内部配列の添え字を計算 */ index = hash(key) % t->size; /* index 番目の連結リストを先頭から順に走査 */ n = t->heads[index]; while (n != NULL) { /* 引数で指定された key とハッシュ表内のキーが一致したら 直ちに対応する値を返す */ if (strcmp(key, n->key) == 0) return n->value; /* 走査を次に進める */ n = n->next; } /* ここではキーに対応する値は非負であると仮定し, 見つからなかったら -1 を返す */ return -1; } /* キー key と値 value のペアをハッシュ表 t に登録し,その通し番号を返す */ int enter(HashTablePtr t, char *key, int value) { HashNodePtr n = NULL, m = NULL; int index; index = hash(key) % t->size; /* 内部配列の添え字を計算 */ /* キーが既に存在しているかどうかチェック(lookup() と同じ処理)*/ n = t->heads[index]; while (n != NULL) { if (strcmp(key, n->key) == 0) return n->id; /* 通し番号を返す */ n = n->next; } /* 新しいノードを生成 */ m = malloc(sizeof(HashNode)); m->key = _strdup(key); m->id = t->serial_id; m->value = value; /* 連結リストの add_first() と同様にして連結リストに追加 */ m->next = t->heads[index]; t->heads[index] = m; t->serial_id++; /* 次の通し番号に更新 */ return m->id; /* 登録したキーと値のペアに付与された通し番号を返す */ } /* * ハッシュ表 t に登録されるキーの配列を返す *(この配列のサイズは get_cardinality() で取得可能) */ char **get_keys(HashTablePtr t) { char **keys = NULL; HashNodePtr n = NULL; int i; keys = malloc(sizeof(char *) * t->serial_id); /* 各連結リストを走査し,配列に詰め込む */ for (i = 0; i < t->size; i++) { n = t->heads[i]; while (n != NULL) { keys[n->id] = n->key; /* 通し番号を配列添え字に */ n = n->next; } } return keys; /* 後で free() する必要あり */ } void print_hashtable(HashTablePtr t) { printf("Taro => %d\n", lookup(t, "Taro")); } /* ハッシュ表の使用例 */ int main(void) { HashTablePtr t = NULL; t = create_hashtable(); enter(t, "Taro", 25); print_hashtable(t); enter(t, "Taro", 35); print_hashtable(t); delete_hashtable(t); }

専門家に質問してみよう