Эффективный способ удалить все не алфавитно-цифровые символы из большого текста - PullRequest
0 голосов
/ 31 января 2019

Мне нужно обработать объемы текста, и один из шагов - удалить все не алфавитно-цифровые символы.Я пытаюсь найти эффективный способ сделать это.

Пока у меня есть две функции:

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

Поиск предложений.Есть ли другой лучший способ сделать это?Улучшить вышесказанное?

Ответы [ 2 ]

0 голосов
/ 31 января 2019

Эффективный способ удаления всех не алфавитно-цифровых символов из большого текста.


В Go «Эффективный способ» означает, что мы запускаем тесты пакетов Go testing.

Ваше описание большого текста расплывчато.Давайте предположим, что он начинался как текст из файла или другого 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])
}

Для большого текста полный текстПроизведения Шекспира в виде []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
$

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)
}()
0 голосов
/ 31 января 2019

Поскольку число выживших рун меньше 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 на месте.Для этого скопируйте выживающие байты в начало среза и обновите его, когда закончите.Это позволяет избежать выделения памяти и накладных расходов в строках.Это изменение аналогично ответу 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]
}

В зависимости от фактических данных один из подходов в этом ответе может быть быстрее, чем подходы в вопросе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...