75 lines
1.8 KiB
Go
75 lines
1.8 KiB
Go
package db
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
"unicode"
|
||
)
|
||
|
||
var punctuationReg = regexp.MustCompile(`[^\p{L}\p{N}]+`)
|
||
|
||
// BigramTokenize 将文本进行二元分词,用于全文检索影子列
|
||
// 规则:
|
||
// 1. 移除非字母数字的标点符号,按空格/标点初步切分块。
|
||
// 2. 对每个块内的 CJK(中日韩)字符,使用滑动窗口进行 2-gram 切分。
|
||
// 3. 对于块内的非 CJK(英文、数字等)字符,按单词整体保留。
|
||
func BigramTokenize(text string) string {
|
||
if text == "" {
|
||
return ""
|
||
}
|
||
|
||
// 1. 初步切分,按非字母数字字符分割
|
||
chunks := punctuationReg.Split(text, -1)
|
||
var allTokens []string
|
||
|
||
for _, chunk := range chunks {
|
||
if chunk == "" {
|
||
continue
|
||
}
|
||
runes := []rune(chunk)
|
||
length := len(runes)
|
||
|
||
var currentWord []rune
|
||
for i := 0; i < length; i++ {
|
||
r := runes[i]
|
||
if isCJK(r) {
|
||
// 遇到中文字符,先冲刷掉之前的英文单词
|
||
if len(currentWord) > 0 {
|
||
allTokens = append(allTokens, string(currentWord))
|
||
currentWord = nil
|
||
}
|
||
// 1-gram
|
||
allTokens = append(allTokens, string(r))
|
||
// 2-gram
|
||
if i < length-1 && isCJK(runes[i+1]) {
|
||
allTokens = append(allTokens, string(runes[i:i+2]))
|
||
}
|
||
} else {
|
||
// 累积英文/数字
|
||
currentWord = append(currentWord, r)
|
||
}
|
||
}
|
||
// 循环结束,冲刷最后一个单词
|
||
if len(currentWord) > 0 {
|
||
allTokens = append(allTokens, string(currentWord))
|
||
}
|
||
}
|
||
|
||
// 4. 去重,减小索引体积
|
||
tokenMap := make(map[string]bool)
|
||
var uniqueTokens []string
|
||
for _, t := range allTokens {
|
||
if !tokenMap[t] {
|
||
tokenMap[t] = true
|
||
uniqueTokens = append(uniqueTokens, t)
|
||
}
|
||
}
|
||
|
||
return strings.Join(uniqueTokens, " ")
|
||
}
|
||
|
||
func isCJK(r rune) bool {
|
||
return unicode.Is(unicode.Han, r) ||
|
||
unicode.In(r, unicode.Hiragana, unicode.Katakana, unicode.Hangul)
|
||
}
|