在Golang中进行不区分大小写的字符串搜索

38

如何以不区分大小写的方式搜索文件中的单词?

例如:

如果我正在搜索文件中的UpdaTe,则如果文件包含update,则应将其作为匹配项选中并计数。


2
你尝试过什么?你看过strings包吗?http://golang.org/pkg/strings/ - elithrar
@Pang 因为我希望搜索和替换不区分大小写。 - user3841581
我修改了标题并创建了一个新问题,使用原始标题 https://dev59.com/qV0a5IYBdhLWcg3wipTk - user7610
4个回答

72

strings.EqualFold()可以检查两个字符串是否相等,而忽略大小写。它还能处理Unicode字符串。要了解更多信息,请参见http://golang.org/pkg/strings/#EqualFold

http://play.golang.org/p/KDdIi8c3Ar

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.EqualFold("HELLO", "hello"))
    fmt.Println(strings.EqualFold("ÑOÑO", "ñoño"))
}

两者都返回 true。


15
这并不是原帖作者真正想要的(尽管原标题曾经这么说)。您无法实际使用此方法在大文件中搜索子串。 - user7610
1
@user7610,“你不能真实地使用它在一个大文件中搜索子字符串。”-> 请说得更详细一些,给出解释并提供链接 :) - Filip Bartuzi
信息:EqualFold报告s和t是否在Unicode折叠大小写下作为UTF-8字符串是“相等的”。 fmt.Println(strings.Contains(strings.ToLower(“Golang”),strings.ToLower(“go”))) // true - Fahri Güreşçi
使用 strings.EqualFold,搜索策略可能是将您的 needle 字符串与 haystack 的每个可能的子字符串进行比较,其长度与 needle 相同。这会得到 O(len(haystack) * len(needle)) 的算法。我想这并不那么糟糕,如果它们适合内存,即使是大文件,也可以很好地使用。 - user7610

18

假设您问题的重点是搜索,而不是从文件中读取部分,因此我将只回答那一部分。

可能最简单的方法是将两个字符串(您要搜索的字符串和要搜索的字符串)转换为全部大写或全部小写,然后进行搜索。例如:

func CaseInsensitiveContains(s, substr string) bool {
    s, substr = strings.ToUpper(s), strings.ToUpper(substr)
    return strings.Contains(s, substr)
}

你可以在这里看到它的实际运行效果。


1
问题的关键在于ToUpper 正是其中的问题所在。每次检查都会有一对内存分配。正确的方法就是不修改数据,在字符串之间逐个比较大写字符(这只是一个位反转)。同时你还需要考虑重新开始匹配的问题。 - Rob
3
这并不意味着答案是错误的,只是效率低下。如果你想修改这个答案,例如保留一个大写字符串的副本,以便每次搜索时无需执行转换,那当然可以这样做。 - joshlf

13

仅在需要精确匹配而不是语言正确的字符串搜索时使用 strings.Contains

除非你只搜索ASCII字符,否则当前的所有答案都不正确。特定的分音符/变音符或其他Unicode字形修饰符,在少数语言(如英语)之外的大多数语言中有用。根据@snap提到的更“正确”的定义,“搜索非ASCII字符”的标准谷歌短语。

为了正确支持语言搜索,您需要使用http://golang.org/x/text/search

func SearchForString(str string, substr string) (int, int) {
    m := search.New(language.English, search.IgnoreCase)
    return = m.IndexString(str, substr)
}

start, end := SearchForString('foobar', 'bar');
if start != -1 && end != -1 {
    fmt.Println("found at", start, end);
}

如果你只需要起始索引:

func SearchForStringIndex(str string, substr string) (int, bool) {
    m := search.New(language.English, search.IgnoreCase)
    start, _ := m.IndexString(str, substr)
    if start == -1 {
        return 0, false
    }
    return start, true
}

index, found := SearchForStringIndex('foobar', 'bar');
if found {
    fmt.Println("match starts at", index);
}

在这里搜索language.Tag结构,以找到您想要搜索的语言,或者如果不确定,请使用language.Und

更新

似乎有些混淆,因此以下示例应有助于澄清事情。

package main

import (
    "fmt"
    "strings"

    "golang.org/x/text/language"
    "golang.org/x/text/search"
)

var s = `Æ`
var s2 = `Ä`

func main() {
    m := search.New(language.Finnish, search.IgnoreDiacritics)
    fmt.Println(m.IndexString(s, s2))
    fmt.Println(CaseInsensitiveContains(s, s2))
}

// CaseInsensitiveContains in string
func CaseInsensitiveContains(s, substr string) bool {
    s, substr = strings.ToUpper(s), strings.ToUpper(substr)
    return strings.Contains(s, substr)
}

关于 ASCII 的起始语句是不正确的。根据 https://golang.org/pkg/strings/#ToUpper,它处理“所有 Unicode 字母”,很容易验证它确实如此。但是,它不处理特定于语言的细节。如果需要处理这些细节,可以按照此答案中所述进行处理。 - snap
@snap 我不是在谈论 strings.ToUpper(),我是在谈论“不区分大小写的字符串搜索”。 - Xeoncross
@Xenocross,不是这样的。例如对于一些非ASCII字符(如å、ä和ö)的芬兰语而言,strings.ToUpper(s) == strings.ToUpper(s2) 完美地工作。 - snap
@snap 适用于芬兰语。请参考添加的示例,其中我们匹配了一个过时的Æ用法。 - Xeoncross
我很快找到的最古老的样本是一本1642年的圣经。它使用了 Ä。那么在芬兰,究竟是什么时候开始使用 Æ 的呢?除非正在构建处理历史文本的专业系统,否则这有何意义? - snap
显示剩余6条评论

10
如果您的文件很大,您可以使用正则表达式和bufio。
//create a regex `(?i)update` will match string contains "update" case insensitive
reg := regexp.MustCompile("(?i)update")
f, err := os.Open("test.txt")
if err != nil {
    log.Fatal(err)
}
defer f.Close()

//Do the match operation
//MatchReader function will scan entire file byte by byte until find the match
//use bufio here avoid load entire file into memory
println(reg.MatchReader(bufio.NewReader(f)))

关于bufio

bufio包实现了一个缓冲读取器,它可能在许多小读取操作中具有高效性,并且还提供了额外的读取方法,因此非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接