最近不知道要幹嘛,就想說用 Python 來寫個簡單的 Spell Checker,用 BKtree + 萊文斯特距離來做,整體來說不難,反正不會複製貼上就好,但是因為萊文斯特距離是用 DP 的方式跑的,所以用 Python 感覺會很慢,就打算用個快速一點的語言來做,試過 Cython、C++ 編譯的 dll,前者遇到的 bug 說要吃 Visual studio 微軟全家桶,後者完全不知道為什麼抓不到 dll。
就在我想繼續(xù) ppyy 的時候,豬腳突然出了一篇 Golang 的教學,就想到 Golang 好像也可以輸出 dll,馬上寫了簡單的 code 就來測試,本來抱著死馬當活馬醫(yī)的心態(tài)去試,結(jié)果...「握操,有ㄟ」好久沒看到我的 Terminal 有那麼的乾淨,接著就來把那段 DP 程式碼搬過去了。查了 "python pass string to golang",debug 了幾個小時,終於寫好了。想說來記錄一下,之後忘記可以來看。
步驟
1. 寫好 go 程式碼,在要輸出的函式上面新增 //export 函式名稱
package main import "unicode/utf8" import "C" import "math" func lenStr(s string) int { return utf8.RuneCountInString(s) } func min(nums ...int) int { minValue := math.MaxInt32 for _, val := range nums { if val < minValue { minValue = val } } return minValue } func max(nums ...int) int { maxValue := math.MinInt32 for _, val := range nums { if val > maxValue { maxValue = val } } return maxValue } //export LevenshteinDistance func LevenshteinDistance(s1 string, s2 string) int { s1Len := lenStr(s1) s2Len := lenStr(s2) if s1Len == 0 || s2Len == 0 { return max(s1Len, s2Len) } dp := make([][]int, s1Len+1) for i := 0; i <= s1Len; i++ { dp[i] = make([]int, s2Len+1) } for i := 1; i <= s1Len; i++ { dp[i][0] = i } for j := 1; j <= s2Len; j++ { dp[0][j] = j } for i := 1; i <= s1Len; i++ { for j := 1; j <= s2Len; j++ { cost := 1 if s1[i-1] == s2[j-1] { cost = 0 } dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+cost) } } return dp[s1Len][s2Len] } func main() { } |
2. 在終端機打上 go build -buildmode c-shared -o 輸出名稱.dll 程式檔案名稱.go
go build -buildmode c-shared -o test.dll test.go |
理論上會出現(xiàn)一個 test.h,偷喵了一下檔案,golang 應該是轉(zhuǎn)成了 c 語言
3. 接著到 python 檔案打程式碼,調(diào)用 ctypes,完工
from ctypes import * class GoString(Structure): _fields_ = [ ('p', c_char_p), ('n', c_longlong) ] edit_distance_lib = cdll.LoadLibrary("./test.dll") s1 = '' s2 = '艾斯瓦羅come' s1_go_str = GoString(s1.encode('utf-8'), len(s1.encode('utf-8'))) s2_go_str = GoString(s2.encode('utf-8'), len(s2.encode('utf-8'))) print(edit_distance_lib.LevenshteinDistance(s1_go_str, s2_go_str)) |
因為豬腳有篇文章有提到不同語言傳字串跟傳整數(shù)浮點數(shù)那種原始資料不太一樣,所以其實這邊我卡了一段時間。去剛剛生成的 .h 檔看 go string 好像會轉(zhuǎn)成 struct { const char* p; ptrdiff_t n; },基本上 p 就是 c 語言的字元陣列,n 就是陣列的 byte 大小。
所以 class GoString 就是模擬底層 C 的 struct,值得一提的是我一開始 ptrdiff_t 的參數(shù)是給 python 字串的長度,以上面 s2 來說就是 8,但是試了一下有中文的話結(jié)果會錯誤。折騰了一段時間才發(fā)現(xiàn)原來 utf-8 的中文字符大小是 3 bytes,英文是 1 byte,難怪會錯誤,改成 encode('utf-8') 後再取 len 就可以了。
Golang 讚...