主題

紀錄些最近學到的程式小知識 - 讚美Golang

魔化鬼鬼 | 2021-10-04 01:20:56 | 巴幣 4 | 人氣 211

        最近不知道要幹嘛,就想說用 Python 來寫個簡單的 Spell Checker,用 BKtree + 萊文斯特距離來做,整體來說不難,反正不會複製貼上就好,但是因為萊文斯特距離是用 DP 的方式跑的,所以用 Python 感覺會很慢,就打算用個快速一點的語言來做,試過 Cython、C++ 編譯的 dll,前者遇到的 bug 說要吃 Visual studio 微軟全家桶,後者完全不知道為什麼抓不到 dll。

        就在我想繼續 ppyy 的時候,豬腳突然出了一篇 Golang 的教學,就想到 Golang 好像也可以輸出 dll,馬上寫了簡單的 code 就來測試,本來抱著死馬當活馬醫的心態去試,結果...「握操,有ㄟ」好久沒看到我的 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
理論上會出現一個 test.h,偷喵了一下檔案,golang 應該是轉成了 c 語言

3. 接著到 python 檔案打程式碼,調用 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))

        因為豬腳有篇文章有提到不同語言傳字串跟傳整數浮點數那種原始資料不太一樣,所以其實這邊我卡了一段時間。去剛剛生成的 .h 檔看 go string 好像會轉成 struct { const char* p; ptrdiff_t n; },基本上 p 就是 c 語言的字元陣列,n 就是陣列的 byte 大小。

        所以 class GoString 就是模擬底層 C 的 struct,值得一提的是我一開始 ptrdiff_t 的參數是給 python 字串的長度,以上面 s2 來說就是 8,但是試了一下有中文的話結果會錯誤。折騰了一段時間才發現原來 utf-8 的中文字符大小是 3 bytes,英文是 1 byte,難怪會錯誤,改成 encode('utf-8') 後再取 len 就可以了。

        Golang 讚...

創作回應

相關創作

更多創作