[Python] 289 ハッシュ関数 FNV-1によるハッシュ値のばらつき

FNV-1により生成されたハッシュ値のばらつきをヒストグラムで確認しました。ハッシュ値は自製のC言語モジュールで生成しました。

上のグラフが1986年以降に生まれた競走馬26.2万頭の馬名ハッシュ値、下のグラフが馬名にシルクが含まれる1000頭のハッシュ値をヒストグラムにしたものです。

満遍なくハッシュ値が生成されており、冠名による偏りもほとんど見られませんでした。

ハッシュ値の重複については調査中です。重複があれば他のハッシュ関数を検討します。

import matplotlib.pyplot as plt
import datetime
import pandas as pd

df = pd.read_csv("name_hash.csv",encoding='UTF-8')
df2 = df[df['馬名'].str.contains('シルク',na=False)]
list = df2['horse_hash'].tolist()

datetime_now = datetime.datetime.now()
datetime_now_str = datetime_now.strftime('%y%m%d%H%M')

plt.hist(list, bins=20,color=['#7fffd4'])
plt.savefig(f"{datetime_now_str}_hist_silk.png")

[Python] 288 CSVファイルから改行コードを削除

一定の頻度で必要になりそうなので書いておきます。

import csv

print("開始年度を入力してください")
start_year = input()

print("終了年度を入力してください")
end_year = input()

for year in range(int(start_year),int(end_year)+1):
    file = f'{year}.csv'
    file_new= f'{year}_new.csv'

    with open (file, mode="r", encoding="UTF-8") as f1:
        with open(file_new, mode="w", encoding="UTF-8") as f2:
            writer = csv.writer(f2)
            for row in csv.reader(f1):
                rows = []
                for i in range(0,len(row)):
                    rows.append(row[i].replace('\n',''))
                writer.writerow(rows)

[Python] 287 引数ありのC言語モジュール ハッシュ関数 FNV

Pythonでもハッシュ関数FNVを使えるようにするためC言語モジュール化しました。

モジュールからの戻り値は整数の範囲でしか使えず、32ビットのunsigned intには対応できましたが、64ビットではうまくいきませんでした。

初めはPythonからポインタをどうやって渡すのか見当もつかなくて、戻り値の受け取り方もかなり試行錯誤しました。

これでC言語とPythonで同一文字列から同じハッシュ値を作成できるので色々使い道がありそうです。

2021/7/25追記:コードを改良して以下の記事にアップしています。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

extern uint32_t fnv_1_hash_32(const char*);

static PyObject* fnv_32(PyObject* self, PyObject* args)
{
    const char* s;
    unsigned int hash=2166136261U;

    if (!PyArg_ParseTuple(args, "s", &s)){
        return NULL;
    }
    else{
        while (*s) {
        hash*=16777619U;
        hash^=*(s++);
        }

        return Py_BuildValue("i", hash);
    }
}

static PyMethodDef fnvmethods[] = {
    {"fnv_1_hash_32", fnv_32, METH_VARARGS},
    {NULL,NULL,0}
};

static struct PyModuleDef fnv = {
    PyModuleDef_HEAD_INIT,
    "fnv",
    "Python3 C API Module(Sample 1)",
    -1,
    fnvmethods
};

PyMODINIT_FUNC PyInit_fnv(void)
{
    return PyModule_Create(&fnv);
}
from c_module import fnv

name = 'シャフリヤール'

hash = fnv.fnv_1_hash_32(name)

# int から unsigned int 32bitへ変換
hash_unsigned32 = hash & 0xffffffff

print(hash_unsigned32)
--------------------------------------------------

出力
--------------------------------------------------
3687658616
#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261U;

    while (*s) {
        hash*=16777619U;
        hash^=*(s++);
    }
    return hash;
}
from distutils.core import setup, Extension

setup(name='fnv',
    version='1.0',
    ext_modules=[Extension('fnv', sources = ['fnv.c','fnv_1.c'])]
)
<セットアップコマンド>

・自作ライブラリに配置するsoファイルを作成するコマンド "from c_module import fnv"
python setup.py build_ext -i

・既存のライブラリにインストールするコマンド "import fnv"
python setup.py install

[Python] 286 C言語実行ファイルの併用 その4 文字列のハッシュ化

これまでC言語を学習してきて、この言語は文字列の取り扱いが不得手というのがよく分かりました。そのような仕様でもstrcmp関数は1文字づつの比較しかできないながら相当速くなっていると思います。

さらなる高速化を検討した結果、文字列を数字に変換して加減計算等できるようにすれば処理が速くなるのではと考え、ハッシュ関数によるハッシュ化を試してみました。

データベースに馬名のハッシュ値を格納し検索に用いたところ、24000件の処理を80秒から55秒に短縮しました。

sscanf関数が扱いづらくatoi関数の必要性にもなかなか気がつかなかったため、かなり難航しました。

今回は比較する文字列の片方を前もってハッシュ化しましたが、両方処理していたらさらに速くなるはずです。

<修正箇所>
uint32_t horse_hash_int;

while(fgets(buf,2000,fp ) != NULL ) {
    if (i != 0){
        ret = sscanf(buf, " %[^,],%[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_hash,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,info,breeder,area,price,prize_money,result,wining_race,relatives) ;
	
        horse_hash_int = atoi(horse_hash);
		
        if(horse_hash_int - fnv_1_hash_32(horse_name_in) == 0){
            fp3 = fopen(fname3, "a");
            fprintf(fp3," %s,%9s\n",horse_name_in,horseID);
            fclose(fp3);
            b ++;
            break;
        }
    }
    i ++ ;
}

[C言語] 06 ハッシュ関数 FNVの外部関数化

FNVをPythonでも使えるようにするため、まずは外部関数化しました。

64バイトハッシュ値のフォーマット指定子は%luです。

#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261U;

    while (*s) {
        hash*=16777619U;
        hash^=*(s++);
    }
    return hash;
}

uint64_t fnv_1_hash_64(char *s)
{
    long unsigned int hash=14695981039346656037U;

    while (*s) {
        hash*=1099511628211LLU;
        hash^=*(s++);
    }
    return hash;
}
#include <stdio.h>
#include <stdint.h>
#include "fnv_1.c"

int main(void)
{
    char *s = "シャフリヤール";

    printf("%u\n",fnv_1_hash_32(s));
    printf("%lu\n",fnv_1_hash_64(s));
}
--------------------------------------------------

出力
--------------------------------------------------
3687658616
7203286604922561048

[C言語] 05 簡易なハッシュ関数 FNV

[Python] 283の記事あたりまで取り組んでいたデータベース検索の高速化ですが、文字列比較ではなくハッシュ値比較でも試してみました。

ハッシュ関数によるハッシュ値算出の時間が余計に掛かっているものの、これまでの最速記録80秒に対して84秒となかなかの健闘でした。正確性も問題ありません。

その他では機械語変換手前のアセンブリ言語コードをチェックしたりもしましたが初級者にいじれるはずもなく、また最近の文字列比較機能(strcmpなど)は優秀のようで小手先の自作関数では歯が立ちませんでした。

ハッシュ関数についてはスキルアップのための余興みたいなもので、ここらで高速化検討は打ち切りにするつもりです。

#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261UL;

    while (*s) {
        hash*=16777619UL;
        hash^=*(s++);
    }
    return hash;
}
<該当箇所のみ>
#include "fnv_1.c"

if (i != 0){
    if (fnv_1_hash_32(horse_name) - fnv_1_hash_32(horse_name_in) == 0){
        fp3 = fopen(fname3, "a");
        fprintf(fp3,"%s,%9s\n",horse_name_in,horseID);
        fclose(fp3);
        b ++;
        break;
    }
}

[C言語] 04 pre K&Rコードのコンパイル&実行 K&R初版 5章2

K&R第2版から遡ること10年、K&R初版が出版された1978年当時のC言語コードいわゆるpre K&Rコードをコンパイル&実行してみました。

手順
1. K&R初版訳本P87のgetch関数ファイルをgetch.cとする。
2. K&R初版訳本P101のgetint関数ファイルをgetint.cとする。
3. main.cを作成する(過去記事[C言語]03参照)。
4. main.cをコンパイルし、a.outを実行する。

[C言語] 03 C89コードのコンパイル&実行 K&R第2版 5章2

K&R第2版が出版された1988年当時のC言語コードをコンパイル&実行してみました。YouTubeの演習解説動画が非常に役に立ちました。

手順
1. K&R第2版訳本P96のgetch関数ファイルをgetch.cとする。
2. K&R第2版訳本P118のgetint関数ファイルをgetint.cとする。
3. 以下のコードをmain.cとする。
4. main.cをコンパイルし、a.outを実行する。

#include <stdio.h>
#include "getch.c"
#include "getint.c"

main()
{
    int ret,c;

    if ((ret= getint(&c))==0)
        printf("not a valid number\n");
    else if (ret > 0)
        printf("valid number\n");
    else
        printf("End of file\n");

    printf("return value: %d\n",ret);

    return 0;
}

[C言語] 02 数値のコピー

変数への数値のコピーは等式による代入で可能です。

sscanfによる読み込みにおいて3番目の引数は格納先のポインタであり、数値の場合は変数名に&を付け(&horseID)、文字列の場合は変数名(horse_name)そのままとなります。

正確には&horse_name[0]ですが、horse_nameは同じ内容とのことです。また&horse_nameとしてもエラーにはなりません。

<過去コードの該当箇所>

int horseID;
int id;
char horse_name[50];

while(fgets(buf,100, fp ) != NULL ) {
    sscanf(buf, "%d, %s",&horseID,horse_name) ; // &horse_nameでも可

    if (i != 0){
        if (strcmp(horse_name,horse_name_in)==0){
            id = horseID; // 数値のコピー
            printf("コピー完了 %d %s\n",id,horse_name);
            break;
        }
    }
    i ++ ;
}

[C言語] 01 文字列のコピー strcpy

文字列のコピーにはstrcpyを使います。末尾の\0も含まれるので文字数は1つ以上多くしないとエラーになります。

当初はこれを知らなくて等式でコピーしようとしていました。コンピュータの仕組みを分かっていないと手詰まりになりますね。

<過去コードの該当箇所>

char horseID[10]; // horseIDは9文字だが末尾の\0も入れて10文字とする
char id[10]

if (i != 0){
    if (strcmp(horse_name,horse_name_in)==0){
        strcpy(id,horseID);
        printf("コピー完了 %s\n",id);
		}
}