golang实现Porter词干提取算法的文本处理插件porter的使用
Golang实现Porter词干提取算法的文本处理插件porter的使用
Porter Stemmer for Go
这是一个对Martin Porter的C语言实现的Porter词干提取算法的直接移植。该移植基于的C语言版本是线程安全版本。
原始算法在以下论文中描述:
M.F. Porter, 1980, An algorithm for suffix stripping, Program, 14(3) pp
130-137.
虽然内部实现和接口与原始实现几乎相同,但Go接口已经大大简化。可以按以下方式调用词干提取器:
import "porter"
...
stemmed := porter.Stem(word_to_stem)
安装
go get github.com/a2800276/porter
使用goinstall安装后,导入方式为:
import "github.com/a2800276/porter"
完整示例
下面是一个完整的示例代码,展示如何使用porter库进行词干提取:
package main
import (
"fmt"
"github.com/a2800276/porter" // 导入porter词干提取库
)
func main() {
// 示例单词列表
words := []string{
"running",
"jumps",
"happily",
"cats",
"conclusion",
}
// 对每个单词进行词干提取
for _, word := range words {
stemmed := porter.Stem(word) // 调用Stem函数提取词干
fmt.Printf("Original: %-10s Stemmed: %s\n", word, stemmed)
}
}
输出结果示例:
Original: running Stemmed: run
Original: jumps Stemmed: jump
Original: happily Stemmed: happili
Original: cats Stemmed: cat
Original: conclusion Stemmed: conclus
限制
虽然实现相当健壮,但这仍然是一个进行中的工作。特别是,可能会提供一个新的接口来防止在string
和[]byte
之间进行过多的转换。目前,在调用Stem
时,字符串参数会被转换为字节切片,算法处理后再转换回字符串返回。
此外,该实现在处理Unicode输入方面不是很健壮,目前只会忽略设置了高位的字节。调用者有责任确保字符串只包含ASCII字符。由于算法本身只对英语单词进行操作,这并不限制功能,但确实是一个不便之处。
TODO
- 提供字节切片API以避免往返于字符串的转换
更多关于golang实现Porter词干提取算法的文本处理插件porter的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现Porter词干提取算法的文本处理插件porter的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang实现Porter词干提取算法
Porter词干提取算法是一种常用的英文词干提取算法,它通过一系列规则将单词转换为其基本形式(词干)。下面我将介绍如何在Go中实现和使用Porter词干提取算法。
使用porter库实现词干提取
Go语言中有一个常用的porter算法实现库github.com/a2800276/porter
。以下是使用示例:
package main
import (
"fmt"
"github.com/a2800276/porter"
"strings"
)
func main() {
// 示例文本
text := "The quick brown foxes are jumping over the lazy dogs"
// 分割文本为单词
words := strings.Fields(text)
// 创建Porter词干提取器实例
stemmer := porter.NewStemmer()
// 对每个单词进行词干提取
for _, word := range words {
stem := stemmer.Stem(word)
fmt.Printf("%s -> %s\n", word, stem)
}
}
纯Go实现Porter算法
如果你想了解算法原理或需要自定义实现,下面是一个完整的Porter词干提取算法的Go实现:
package porterstemmer
import (
"strings"
)
// 判断是否是辅音
func isConsonant(s []rune, i int) bool {
switch s[i] {
case 'a', 'e', 'i', 'o', 'u':
return false
case 'y':
if i == 0 {
return true
}
return !isConsonant(s, i-1)
default:
return true
}
}
// 计算辅音序列数量
func measure(s []rune) int {
count := 0
i := 0
n := len(s)
// 跳过开头的辅音
for i < n && isConsonant(s, i) {
i++
}
// 计算VC的数量
for i < n {
// 找元音
for i < n && !isConsonant(s, i) {
i++
}
if i >= n {
break
}
count++
// 找辅音
for i < n && isConsonant(s, i) {
i++
}
}
return count
}
// 判断是否包含元音
func containsVowel(s []rune) bool {
for i := range s {
if !isConsonant(s, i) {
return true
}
}
return false
}
// 判断是否以双辅音结尾
func endsWithDoubleConsonant(s []rune) bool {
n := len(s)
if n < 2 {
return false
}
return s[n-1] == s[n-2] && isConsonant(s, n-1)
}
// 判断是否以cvc结尾
func endsWithCVC(s []rune) bool {
n := len(s)
if n < 3 {
return false
}
return isConsonant(s, n-3) && !isConsonant(s, n-2) && isConsonant(s, n-1) &&
s[n-1] != 'w' && s[n-1] != 'x' && s[n-1] != 'y'
}
// Porter词干提取主函数
func Stem(word string) string {
s := []rune(strings.ToLower(word))
if len(s) < 3 {
return word
}
// 步骤1a
if len(s) > 3 && s[len(s)-1] == 's' {
switch s[len(s)-2] {
case 'e':
if len(s) > 4 && s[len(s)-3] == 'i' {
s = s[:len(s)-3]
} else if len(s) > 3 && s[len(s)-3] == 's' {
// 什么都不做
} else {
s = s[:len(s)-1]
}
case 's':
// 什么都不做
default:
s = s[:len(s)-1]
}
}
// 步骤1b
if measure(s) > 0 {
if len(s) >= 4 && strings.HasSuffix(string(s), "eed") {
s = s[:len(s)-1]
} else if containsVowel(s[:len(s)-2]) && strings.HasSuffix(string(s), "ed") {
s = s[:len(s)-2]
// 处理特殊情况
if strings.HasSuffix(string(s), "at") || strings.HasSuffix(string(s), "bl") || strings.HasSuffix(string(s), "iz") {
s = append(s, 'e')
} else if endsWithDoubleConsonant(s) && s[len(s)-1] != 'l' && s[len(s)-1] != 's' && s[len(s)-1] != 'z' {
s = s[:len(s)-1]
} else if measure(s) == 1 && endsWithCVC(s) {
s = append(s, 'e')
}
} else if containsVowel(s[:len(s)-3]) && strings.HasSuffix(string(s), "ing") {
s = s[:len(s)-3]
// 处理特殊情况
if strings.HasSuffix(string(s), "at") || strings.HasSuffix(string(s), "bl") || strings.HasSuffix(string(s), "iz") {
s = append(s, 'e')
} else if endsWithDoubleConsonant(s) && s[len(s)-1] != 'l' && s[len(s)-1] != 's' && s[len(s)-1] != 'z' {
s = s[:len(s)-1]
} else if measure(s) == 1 && endsWithCVC(s) {
s = append(s, 'e')
}
}
}
// 步骤1c
if len(s) > 0 && s[len(s)-1] == 'y' && containsVowel(s[:len(s)-1]) {
s[len(s)-1] = 'i'
}
// 步骤2
if measure(s) > 0 {
switch {
case strings.HasSuffix(string(s), "ational"):
s = append(s[:len(s)-5], 'e')
case strings.HasSuffix(string(s), "tional"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "enci"):
s[len(s)-1] = 'e'
case strings.HasSuffix(string(s), "anci"):
s[len(s)-1] = 'e'
case strings.HasSuffix(string(s), "izer"):
s = s[:len(s)-1]
case strings.HasSuffix(string(s), "abli"):
s[len(s)-1] = 'e'
case strings.HasSuffix(string(s), "alli"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "entli"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "eli"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ousli"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ization"):
s = append(s[:len(s)-5], 'e')
case strings.HasSuffix(string(s), "ation"):
s = append(s[:len(s)-3], 'e')
case strings.HasSuffix(string(s), "ator"):
s = append(s[:len(s)-2], 'e')
case strings.HasSuffix(string(s), "alism"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "iveness"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "fulness"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "ousness"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "aliti"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "iviti"):
s = append(s[:len(s)-3], 'e')
case strings.HasSuffix(string(s), "biliti"):
s = append(s[:len(s)-5], 'e')
}
}
// 步骤3
if measure(s) > 0 {
switch {
case strings.HasSuffix(string(s), "icate"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ative"):
s = s[:len(s)-5]
case strings.HasSuffix(string(s), "alize"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "iciti"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ical"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ful"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ness"):
s = s[:len(s)-4]
}
}
// 步骤4
if measure(s) > 1 {
switch {
case strings.HasSuffix(string(s), "al"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ance"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "ence"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "er"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ic"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "able"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "ible"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "ant"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ement"):
s = s[:len(s)-5]
case strings.HasSuffix(string(s), "ment"):
s = s[:len(s)-4]
case strings.HasSuffix(string(s), "ent"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ion"):
if len(s) > 3 && (s[len(s)-4] == 's' || s[len(s)-4] == 't') {
s = s[:len(s)-3]
}
case strings.HasSuffix(string(s), "ou"):
s = s[:len(s)-2]
case strings.HasSuffix(string(s), "ism"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ate"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "iti"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ous"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ive"):
s = s[:len(s)-3]
case strings.HasSuffix(string(s), "ize"):
s = s[:len(s)-3]
}
}
// 步骤5a
if measure(s) > 1 && s[len(s)-1] == 'e' {
s = s[:len(s)-1]
} else if measure(s) == 1 && !endsWithCVC(s) && s[len(s)-1] == 'e' {
s = s[:len(s)-1]
}
// 步骤5b
if measure(s) > 1 && endsWithDoubleConsonant(s) && s[len(s)-1] == 'l' {
s = s[:len(s)-1]
}
return string(s)
}
使用示例
package main
import (
"fmt"
"porterstemmer" // 假设上面的实现保存在porterstemmer包中
)
func main() {
words := []string{
"running", "jumps", "easily", "happily", "cats", "dogs",
"ponies", "caresses", "universal", "conditional",
}
for _, word := range words {
stem := porterstemmer.Stem(word)
fmt.Printf("%s -> %s\n", word, stem)
}
}
性能考虑
对于大规模文本处理,建议:
- 重用Stemmer实例而不是每次创建新的
- 考虑并行处理多个单词
- 预处理文本时去除标点符号和特殊字符
Porter算法虽然简单高效,但它有一些局限性,比如无法处理不规则变形,也无法区分同形异义词。对于更高级的需求,可能需要考虑Snowball算法或其他更复杂的词干提取器。
希望这个实现对你有所帮助!