问题 如何在Go中创建不区分大小写的地图?


我想要一个键不敏感的字符串作为键。 它是由语言支持还是我必须自己创建? 谢谢

编辑: 我正在寻找的是默认情况下制作它的方法,而不是每次我使用地图时都要记住转换键。


12147
2018-06-20 17:12


起源

每次手动映射到Unicode折叠。 - tchrist
SCL,你是否关注这种情况下的Unicode?也就是说,您的需求是否包括拒绝意外的Unicode代码点或仔细关注处理预期的Unicode代码点? - Sonia
@sonia,嗨,我只考虑ASCII。但既然你在问,我将如何处理Unicode? - Santiago Corredoira
@SCL对于非ASCII,你有一个问题,因为Go并不是我所知的提供的 toFoldcase 映射使这可行。 Sonya的代码仅适用于ASCII,但在Unicode上搞砸了。 - tchrist
我知道有很多问题。我认为这应该是一个单独的问题,理想情况是基于你的情况。告诉您数据的来源,您期望的数据,您想要的结果类型。 - Sonia


答案:


编辑:我的初始代码实际上仍然允许映射语法,因此允许绕过方法。这个版本更安全。

你可以“派生”一种类型。在Go我们只是说声明。然后在类型上定义方法。它只需要一个非常薄的包装器来提供您想要的功能。但请注意,您必须使用普通方法调用语法调用get和set。没有办法保持内置映射的索引语法或可选的ok结果。

package main

import (
    "fmt"
    "strings"
)

type ciMap struct {
    m map[string]bool
}

func newCiMap() ciMap {
    return ciMap{m: make(map[string]bool)}
}

func (m ciMap) set(s string, b bool) {
    m.m[strings.ToLower(s)] = b
}

func (m ciMap) get(s string) (b, ok bool) {
    b, ok = m.m[strings.ToLower(s)]
    return
}

func main() {
    m := newCiMap()
    m.set("key1", true)
    m.set("kEy1", false)
    k := "keY1"
    b, _ := m.get(k)
    fmt.Println(k, "value is", b)
}

10
2018-06-20 18:16



映射到小写不适用于Unicode数据,仅适用于ASCII。你应该在这里映射到Unicode foldcase,而不是小写。否则你的是一个Sisyphean任务,因为小写 Σίσυφος 是 σίσυφος,虽然它的大写小写, ΣΊΣΥΦΟΣ,是对的 σίσυφοσ,这确实是所有这些的折叠。你现在明白为什么Unicode有一个单独的地图吗?案例映射过于复杂,无法盲目映射到任何非为此明确目的而设计的内容,因此Unicode套管表中存在第4个案例图:大写,标题,小写,折叠。 - tchrist
这不是FUD,@ Sonia,这是事实。您不能在Unicode中使用全小写或全大写来不敏感地测试用例。 Unicode套管太复杂了,它也与标准化无关。简单而不完整的casemapping足以证明其固有的缺陷: toLower(ΣΊΣΥΦΟΣ) 和 toLower(Σίσυφος) 虽然他们的原件是彼此不区分大小写的匹配,但是不相等。您必须在Unicode中使用foldcase。因此,您的代码存在问题,并且不符合规定的要求。 - tchrist
要求是字符串。 Go将Unicode用于字符串,而不是ASCII。他们要求一个不区分大小写的地图。您提供了一个仅限ASCII的解决方案,而没有晚上打扰提到这一点。我的评论完全是关于主题的,因为你没有回答问题和措辞的问题,它没有ASCII限制。现在,事实证明这个人实际上除了ASCII之外什么都没有,所以即使在一般情况下它是错误的,你的解决方案也会被偷走。编写适用于Unicode的解决方案,它们也适用于ASCII - 但反过来却不成立,这就是为什么你的代码有问题。 - tchrist
@tchrist也许你可以用你认为正确的实现提供你自己的答案。 - jimt
@jimt使用的东西 EqualFold 这两个字符串将是朝着正确方向迈出的一步。 - tchrist


两种可能性:

  1. 如果输入集保证仅限于转换为大写/小写将产生正确结果的字符,则转换为大写/小写(某些Unicode字符可能不是)

  2. 否则转换为Unicode折叠大小写:

使用 unicode.SimpleFold(rune) 将unicode符文转换为折叠大小写。显然,这比简单的ASCII风格的案例映射更加昂贵,但它对其他语言的可移植性也更高。看到 EqualsFold的源代码 了解如何使用它,包括如何从源字符串中提取Unicode符文。

显然,您将此功能抽象到一个单独的包中,而不是在使用地图的任何地方重新实现它。这应该不言而喻,但是你永远不会知道。


3
2018-06-20 17:21



但这可能是容易出错的,因为它可能会被暴露为一个库,或者我可能会忘记这样做。有没有办法创建一个可以自动执行的派生类型? - Santiago Corredoira
这是完全错误的。 您 必须 使用Unicode大小写折叠规则。 考虑一下 S, s,和 ſ 所有情况都是不区分大小写的 Σ, ς,和 σ。此外, TSCHÜSS, TSCHÜẞ, tschüß, tschüss 都是不区分大小写的。你无法做你假装的事情 - 将所有内容映射到全部大写或全部小写。这根本行不通。 - tchrist
如果你知道你只会处理ASCII,这可能是他的用例,它工作正常。 - Running Wild
“映射以删除案例”是Unicode的特殊第4个案例图(大写,标题,小写,折叠)是专门为其设计的。它解决了这种无壳问题。这样,您就无法构建一个在任意代码点上使用时无声且神秘地失败的数据结构。如果您需要20世纪60年代风格的ASCII而不是任意符文,那么您必须检查它。当Go非常明确地使用完整的Unicode时,将ASCII假设构建到数据中是很糟糕的。 - tchrist
@tchrist虽然我同意你对处理Unicode数据的固有复杂性的看法;作为一个实施点, 对 这样做的方式并不总是如此 对 这样做的方式。事实上,“tschüß”和“tschüss”可能相当于任何说德语的人,声称它们等效的比较函数可能是一个错误,因为它是一个特征,取决于用户期望它做什么。正确是一个坚持期望而不是教条的问题,书籍可以写成(已经写成)关于由此产生的错误 正确 虽然出乎意料的行为。 - tylerl