如何定义自定义字母顺序来比较和排序 Go 中的字符串?
从现在开始,努力学习吧!本文《如何定义自定义字母顺序来比较和排序 Go 中的字符串?》主要讲解了等等相关知识点,我会在golang学习网中持续更新相关的系列文章,欢迎大家关注并积极留言建议。下面就先一起来看一下本篇正文内容吧,希望能帮到你!
在将其标记为重复之前,请先阅读底部
我希望能够按字母顺序对字符串数组(或基于一个字符串值的结构切片)进行排序,但基于自定义字母表或 unicode 字母。
大多数时候,人们建议使用支持不同预定义语言环境/字母的整理器。 (请参阅 java 的答案),但是对于这些语言环境包中不可用的罕见语言/字母表该怎么办?
我想要使用的语言在 golangs 整理支持和可用的语言列表中不可用,因此我需要能够定义自定义字母表或顺序用于排序的 unicode 字符/符文。
其他人建议先将字符串翻译成英语/ascii 可排序字母表,然后再对其进行排序。这就是这个用 javascript 完成的解决方案或这个用 ruby 完成的解决方案中的类似问题所建议的。 但是肯定有一种更有效的方法可以用 go 来做到这一点。
是否可以在 go 中创建一个使用自定义字母/字符集的 collator?这就是 func newfromtable 的用途吗?
看来我应该能够使用重新排序功能,但看起来这尚未在该语言中实现?源代码显示:
func Reorder(s ...string) Option { // TODO: need fractional weights to implement this. panic("TODO: implement") }
如何在 go 中定义用于比较和排序字符串的自定义字母顺序?
解决方案
事先注意:
以下解决方案已被清理和优化,并在此处作为可重用库发布:github.com/icza/abcsort
。
使用 abcsort
,对字符串切片进行自定义排序(使用自定义字母表)非常简单:
sorter := abcsort.new("bac") ss := []string{"abc", "bac", "cba", "ccc"} sorter.strings(ss) fmt.println(ss) // output: [ccc bac abc cba]
按结构体字段之一对结构体切片进行自定义排序如下:
type person struct { name string age int } ps := []person{{name: "alice", age: 21}, {name: "bob", age: 12}} sorter.slice(ps, func(i int) string { return ps[i].name }) fmt.println(ps) // output: [{bob 12} {alice 21}]
原答案如下:
我们可以实现使用自定义字母表的自定义排序。我们只需要创建适当的 less(i, j int) bool
函数,sort
包将完成剩下的工作。
问题是如何创建这样的 less()
函数?
让我们从定义自定义字母表开始。方便的方法是创建一个 string
,其中包含自定义字母表中的字母,从最小到最大枚举(排序)。例如:
const alphabet = "bca"
让我们根据这个字母表创建一个地图,它将告诉我们自定义字母表中每个字母的重量或顺序:
var weights = map[rune]int{} func init() { for i, r := range alphabet { weights[r] = i } }
(注意:上述循环中的 i
是字节索引,而不是 rune
索引,但由于两者都是单调递增,因此两者都适合符文权重。)
现在我们可以创建 less()
函数。为了获得“可接受的”性能,我们应该避免将输入 string
值转换为字节或符文切片。为此,我们可以从 utf8.DecodeRuneInString()
函数调用 help,该函数对 string
的第一个 rune
进行解码。
所以我们逐个符文进行比较。如果两个符文都是自定义字母表中的字母,我们可以使用它们的权重来告诉它们如何相互比较。如果至少有一个符文不是来自我们的自定义字母表,我们将回退到简单的数字符文比较。
如果 2 个输入字符串开头的 2 个符文相等,我们将继续处理每个输入字符串中的下一个符文。我们可以通过对输入字符串进行切片来执行此操作:对它们进行切片不会进行复制,它只是返回一个指向原始字符串数据的新字符串头。
好吧,现在让我们看看这个 less()
函数的实现:
func less(s1, s2 string) bool { for { switch e1, e2 := len(s1) == 0, len(s2) == 0; { case e1 && e2: return false // both empty, they are equal (not less) case !e1 && e2: return false // s1 not empty but s2 is: s1 is greater (not less) case e1 && !e2: return true // s1 empty but s2 is not: s1 is less } r1, size1 := utf8.decoderuneinstring(s1) r2, size2 := utf8.decoderuneinstring(s2) // check if both are custom, in which case we use custom order: custom := false if w1, ok1 := weights[r1]; ok1 { if w2, ok2 := weights[r2]; ok2 { custom = true if w1 != w2 { return w1 < w2 } } } if !custom { // fallback to numeric rune comparison: if r1 != r2 { return r1 < r2 } } s1, s2 = s1[size1:], s2[size2:] } }
让我们看看这个 less()
函数的一些简单测试:
pairs := [][2]string{ {"b", "c"}, {"c", "a"}, {"b", "a"}, {"a", "b"}, {"bca", "bac"}, } for _, pair := range pairs { fmt.printf("\"%s\" < \"%s\" ? %t\n", pair[0], pair[1], less(pair[0], pair[1])) }
输出(在 Go Playground 上尝试):
"b" < "c" ? true "c" < "a" ? true "b" < "a" ? true "a" < "b" ? false "bca" < "bac" ? true
现在让我们在实际排序中测试这个 less()
函数:
ss := []string{ "abc", "abca", "abcb", "abcc", "bca", "cba", "bac", } sort.slice(ss, func(i int, j int) bool { return less(ss[i], ss[j]) }) fmt.println(ss)
输出(在 Go Playground 上尝试):
[bca bac cba abc abcb abcc abca]
同样,如果性能对您很重要,您不应该使用 sort.Slice()
,因为它必须在幕后使用反射,而是创建您自己的切片类型来实现 sort.Interface
,并且在您的实现中,您可以告诉如何在不使用反射的情况下执行此操作使用反射。
它可能是这样的:
type custstrslice []string func (c custstrslice) len() int { return len(c) } func (c custstrslice) less(i, j int) bool { return less(c[i], c[j]) } func (c custstrslice) swap(i, j int) { c[i], c[j] = c[j], c[i] }
当您想使用自定义字母表对字符串切片进行排序时,只需将切片转换为 custstrslice
,这样它就可以直接传递给 sort.Sort()
(此类型转换不会复制切片或其元素,它只是更改类型信息):
ss := []string{ "abc", "abca", "abcb", "abcc", "bca", "cba", "bac", } sort.sort(custstrslice(ss)) fmt.println(ss)
上面的输出再次(在 Go Playground 上尝试):
[bca bac cba abc abcb abcc abca]
一些注意事项:
默认字符串比较按字节比较字符串。也就是说,如果输入字符串包含无效的 utf-8 序列,则仍将使用实际字节。
我们的解决方案在这方面有所不同,因为我们解码符文(我们必须这样做,因为我们使用自定义字母表,其中我们允许符文不一定映射到 utf-8 编码中的字节 1 到 1)。这意味着如果输入不是有效的 utf-8 序列,则行为可能与默认顺序不一致。但如果您的输入是有效的 utf-8 序列,这将执行您期望的操作。
最后一点:
我们已经了解了如何对字符串切片进行自定义排序。如果我们有一个结构体切片(或结构体指针切片),排序算法(less()
函数)可能是相同的,但是在比较切片的元素时,我们必须比较元素的字段,不是结构元素本身。
假设我们有以下结构:
type person struct { name string age int } func (p *person) string() string { return fmt.sprint(*p) }
(添加了 string()
方法,因此我们将看到结构的实际内容,而不仅仅是它们的地址...)
假设我们要使用 person
元素的 name
字段对 []*person
类型的切片应用自定义排序。所以我们简单地定义这个自定义类型:
type personslice []*person func (p personslice) len() int { return len(p) } func (p personslice) less(i, j int) bool { return less(p[i].name, p[j].name) } func (p personslice) swap(i, j int) { p[i], p[j] = p[j], p[i] }
仅此而已。其余的都是一样的,例如:
ps := []*person{ {name: "abc"}, {name: "abca"}, {name: "abcb"}, {name: "abcc"}, {name: "bca"}, {name: "cba"}, {name: "bac"}, } sort.sort(personslice(ps)) fmt.println(ps)
输出(在 Go Playground 上尝试):
[{bca 0} {bac 0} {cba 0} {abc 0} {abcb 0} {abcc 0} {abca 0}]
使用 table_test.go
[1] 作为起点,我得出以下结论。这
真正的工作是由 builder.add
[2] 完成的:
package main import ( "golang.org/x/text/collate" "golang.org/x/text/collate/build" ) type entry struct { r rune w int } func newcollator(ents []entry) (*collate.collator, error) { b := build.newbuilder() for _, ent := range ents { err := b.add([]rune{ent.r}, [][]int{{ent.w}}, nil) if err != nil { return nil, err } } t, err := b.build() if err != nil { return nil, err } return collate.newfromtable(t), nil }
结果:
package main import "fmt" func main() { a := []entry{ {'a', 3}, {'b', 2}, {'c', 1}, } c, err := newCollator(a) if err != nil { panic(err) } x := []string{"alfa", "bravo", "charlie"} c.SortStrings(x) fmt.Println(x) // [charlie bravo alfa] }
- https://github.com/golang/text/blob/3115f89c/collate/table_test.go
- https://pkg.go.dev/golang.org/x/text/collate/build#Builder.Add
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于Golang的相关知识,也可关注golang学习网公众号。

- 上一篇
- 我国千兆城市数量突破 200 个,可覆盖超 5 亿户家庭

- 下一篇
- 广汽埃安AION S MAX星瀚大降价,高性能电动车仅售17.99万元起
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 477浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- AI Make Song
- AI Make Song是一款革命性的AI音乐生成平台,提供文本和歌词转音乐的双模式输入,支持多语言及商业友好版权体系。无论你是音乐爱好者、内容创作者还是广告从业者,都能在这里实现“用文字创造音乐”的梦想。平台已生成超百万首原创音乐,覆盖全球20个国家,用户满意度高达95%。
- 2次使用
-
- SongGenerator
- 探索SongGenerator.io,零门槛、全免费的AI音乐生成器。无需注册,通过简单文本输入即可生成多风格音乐,适用于内容创作者、音乐爱好者和教育工作者。日均生成量超10万次,全球50国家用户信赖。
- 2次使用
-
- BeArt AI换脸
- 探索BeArt AI换脸工具,免费在线使用,无需下载软件,即可对照片、视频和GIF进行高质量换脸。体验快速、流畅、无水印的换脸效果,适用于娱乐创作、影视制作、广告营销等多种场景。
- 2次使用
-
- 协启动
- SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
- 9次使用
-
- Brev AI
- 探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
- 10次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览