ETH官方钱包

前往
大廳
主題

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

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

        最近不知道要幹嘛,就想說用 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 讚...

創(chuàng)作回應

相關(guān)創(chuàng)作

更多創(chuàng)作