首页 > 解决方案 > 从大文本中删除所有非字母数字字符的有效方法

问题描述

我需要处理大量文本,其中一个步骤是删除所有非字母数字字符。我试图找到一种有效的方法来做到这一点。

到目前为止,我有两个功能:

func stripMap(str, chr string) string {
    return strings.Map(func(r rune) rune {
        if strings.IndexRune(chr, r) < 0 {
            return r
        }
        return -1
    }, str)
}

在这里,我实际上必须提供所有非字母字符的字符串。

和普通的旧正则表达式

func stripRegex(in string) string {
    reg, _ := regexp.Compile("[^a-zA-Z0-9 ]+")
    return reg.ReplaceAllString(in, "")
}

正则表达式似乎要慢得多

BenchmarkStripMap-8        30000         37907 ns/op        8192 B/op          2 allocs/op

BenchmarkStripRegex-8          10000        131449 ns/op       57552 B/op         35 allocs/op

寻找建议。还有其他更好的方法吗?改善以上?

标签: go

解决方案


因为幸存的 rune 小于utf8.RuneSelf,所以这个问题可以通过对字节进行操作来解决。如果任何字节不在 中[^a-zA-Z0-9 ],则该字节是要删除的符文的一部分。

func strip(s string) string {
    var result strings.Builder
    for i := 0; i < len(s); i++ {
        b := s[i]
        if ('a' <= b && b <= 'z') ||
            ('A' <= b && b <= 'Z') ||
            ('0' <= b && b <= '9') ||
            b == ' ' {
            result.WriteByte(b)
        }
    }
    return result.String()
}

此函数的一个变体是通过调用 result.Grow 预分配结果:

func strip(s string) string {
    var result strings.Builder
    result.Grow(len(s))
    ...

这可确保函数只分配一次内存,但如果幸存符文与源符文的比率较低,则内存分配可能会显着大于所需。

此答案中的strip函数是为使用string参数和结果类型而编写的,因为这些是问题中使用的类型。

如果应用程序正在处理[]byte源文本并且可以修改该源文本,那么更新[]byte就地会更有效。为此,请将幸存的字节复制到切片的开头,并在完成后重新切片。这避免了 strings.Builder 中的内存分配和开销。这种变化类似于 peterSO 对这个问题的回答。

func strip(s []byte) []byte {
    n := 0
    for _, b := range s {
        if ('a' <= b && b <= 'z') ||
            ('A' <= b && b <= 'Z') ||
            ('0' <= b && b <= '9') ||
            b == ' ' {
            s[n] = b
            n++
        }
    }
    return s[:n]
}

根据使用的实际数据,此答案中的一种方法可能比问题中的方法更快。


推荐阅读