从大文本中删除所有非字母数字字符的有效方法

r.s*_*cky 5 go

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

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

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

在这里,我实际上必须输入一个包含所有非字母字符的字符串。

和普通的旧正则表达式

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

正则表达式似乎要慢得多

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

BenchmarkStripRegex-8          10000        131449 ns/op       57552 B/op         35 allocs/op
Run Code Online (Sandbox Code Playgroud)

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

Cer*_*món 8

因为幸存的符文少于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()
}
Run Code Online (Sandbox Code Playgroud)

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

func strip(s string) string {
    var result strings.Builder
    result.Grow(len(s))
    ...
Run Code Online (Sandbox Code Playgroud)

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

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]
}
Run Code Online (Sandbox Code Playgroud)

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


pet*_*rSO 6

从大文本中删除所有非字母数字字符的有效方法。


在 Go 中,“高效方式”意味着我们运行 Gotesting包基准测试。

您对大文本的描述含糊不清。我们假设它以来自文件或其他byte切片的文本开始。

您可能会产生string([]byte)、 、make([]byte)和 的开销string([]byte)

您可以使用来减少和 几个 的strings.Builder开销。string([]byte)make([]byte)

string([]byte)您可以通过从函数开始进一步减少这一点clean([]byte) string

例如,

func clean(s []byte) string {
    j := 0
    for _, b := range s {
        if ('a' <= b && b <= 'z') ||
            ('A' <= b && b <= 'Z') ||
            ('0' <= b && b <= '9') ||
            b == ' ' {
            s[j] = b
            j++
        }
    }
    return string(s[:j])
}
Run Code Online (Sandbox Code Playgroud)

对于大型文本,莎士比亚全集作为[]byte

$ go fmt && go test strip_test.go -bench=. -benchmem
BenchmarkSendeckyMap-8       20     65988121 ns/op    11730958 B/op      2 allocs/op
BenchmarkSendeckyRegex-8      5    242834302 ns/op    40013144 B/op    130 allocs/op
BenchmarkThunder-8          100     21791532 ns/op    34682926 B/op     43 allocs/op
BenchmarkPeterSO-8          100     16172591 ns/op     5283840 B/op      1 allocs/op
$
Run Code Online (Sandbox Code Playgroud)

strip_test.go:

package main

import (
    "io/ioutil"
    "regexp"
    "strings"
    "testing"
)

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

var alphanum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "

func BenchmarkSendeckyMap(b *testing.B) {
    for N := 0; N < b.N; N++ {
        b.StopTimer()
        bytShakespeare := []byte(strShakespeare)
        b.StartTimer()
        strShakespeare = string(bytShakespeare)
        stripMap(strShakespeare, alphanum)
    }
}

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

func BenchmarkSendeckyRegex(b *testing.B) {
    for N := 0; N < b.N; N++ {
        b.StopTimer()
        bytShakespeare := []byte(strShakespeare)
        b.StartTimer()
        strShakespeare = string(bytShakespeare)
        stripRegex(strShakespeare)
    }
}

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()
}

func BenchmarkThunder(b *testing.B) {
    for N := 0; N < b.N; N++ {
        b.StopTimer()
        bytShakespeare := []byte(strShakespeare)
        b.StartTimer()
        strShakespeare = string(bytShakespeare)
        strip(strShakespeare)
    }
}

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

func BenchmarkPeterSO(b *testing.B) {
    for N := 0; N < b.N; N++ {
        b.StopTimer()
        bytShakespeare := []byte(strShakespeare)
        b.StartTimer()
        clean(bytShakespeare)
    }
}

var strShakespeare = func() string {
    // The Complete Works of William Shakespeare by William Shakespeare
    // http://www.gutenberg.org/files/100/100-0.txt
    data, err := ioutil.ReadFile(`/home/peter/shakespeare.100-0.txt`)
    if err != nil {
        panic(err)
    }
    return string(data)
}()
Run Code Online (Sandbox Code Playgroud)