我想找出一个符文的Unicode属性,特别是其脚本属性的值。 Unicode就是这样说的(在http://www.unicode.org/reports/tr24/第1.5节中):
id
Go的The script property assigns a single value to each character, either
explicitly associating it with a particular script, or assigning one
of several specail [sic] values.
包为我提供了一种方式来问:“脚本x中的这个符文是什么?”,但我没办法问,“这个符文的脚本是什么?”。我显然可以迭代所有脚本,但这样会浪费。找到一个符文的剧本是否有更聪明的方法? (我总是可以实现一个自组织列表,但我正在寻找已经做了我想要的标准go库中的东西,而且我忽略了。)
全部谢谢!
答案 0 :(得分:5)
最简单,最快捷的解决方案是编写函数。例如,
package main
import (
"fmt"
"unicode"
)
var runeScript map[rune]string
func init() {
const nChar = 128172 // Version 9.0.0
runeScript = make(map[rune]string, nChar*125/100)
for s, rt := range unicode.Scripts {
for _, r := range rt.R16 {
for i := r.Lo; i <= r.Hi; i += r.Stride {
runeScript[rune(i)] = s
}
}
for _, r := range rt.R32 {
for i := r.Lo; i <= r.Hi; i += r.Stride {
runeScript[rune(i)] = s
}
}
}
}
func script(r rune) string {
return runeScript[r]
}
func main() {
chars := []rune{' ', '0', 'a', 'α', 'А', 'ㄱ'}
for _, c := range chars {
s := script(c)
fmt.Printf("%q %s\n", c, s)
}
}
输出:
$ go run script.go
' ' Common
'0' Common
'a' Latin
'α' Greek
'А' Cyrillic
'ㄱ' Hangul
$
答案 1 :(得分:3)
彼得的答案很清楚。尽管如此,它在内存使用方面并不容易,因为它在地图中存储了超过十万个条目,值为string
类型。即使string
值只是一个存储指针和长度的标题(请参阅reflect.StringHeader
),但在地图中有这么多的数据仍然是多MB(如6 MB)!
由于可能的不同string
值(不同的脚本名称)的数量很小(137),我们可能会选择使用值类型byte
,它只是切片中的索引存储真实的脚本名称。
这就是它的样子:
var runeScript map[rune]byte
var names = []string{""}
func init() {
const nChar = 128172 // Version 9.0.0
runeScript = make(map[rune]byte, nChar*125/100)
for s, rt := range unicode.Scripts {
idx := byte(len(names))
names = append(names, s)
for _, r := range rt.R16 {
for i := r.Lo; i <= r.Hi; i += r.Stride {
runeScript[rune(i)] = idx
}
}
for _, r := range rt.R32 {
for i := r.Lo; i <= r.Hi; i += r.Stride {
runeScript[rune(i)] = idx
}
}
}
}
func script(r rune) string {
return names[runeScript[r]]
}
func main() {
chars := []rune{' ', '0', 'a', 'α', 'А', 'ㄱ'}
for _, c := range chars {
s := script(c)
fmt.Printf("%q %s\n", c, s)
}
}
与使用map[rune]string
相比,这种简单的改进只需要三分之一的内存。输出相同(在Go Playground上尝试):
' ' Common
'0' Common
'a' Latin
'α' Greek
'А' Cyrillic
'ㄱ' Hangul
使用map[rune]byte
将导致2 MB的RAM使用量,并且需要&#34;某些&#34;是时候构建这张地图了,这可能是也可能是不可接受的。
还有另一种方法/解决方案。我们可能会选择不构建&#34;全部&#34;符文,但只存储所有范围的切片(实际上是2个范围切片,一个具有16位Unicode值,另一个具有32位Unicode代码点)。
这样做的好处源于这样一个事实:范围的数量远远少于符文的数量:只有852(相比之下,100,000 +符文)。与解决方案#1相比,具有总共852个元素的2个切片的存储器使用将是可忽略的。
在我们的范围内,我们还存储脚本(名称),因此我们可以返回此信息。我们也可以只存储一个名称索引(如解决方案#1),但由于我们只有852个范围,所以它不值得。
我们对范围切片进行排序,因此我们可以在其中使用二进制搜索(切片中约400个元素,二进制搜索:我们得到的结果最多为7步,最坏情况下重复二进制搜索:15步)。
好的,让我们看看。我们正在使用这些范围包装器:
type myR16 struct {
r16 unicode.Range16
script string
}
type myR32 struct {
r32 unicode.Range32
script string
}
将它们存储在:
var allR16 = []*myR16{}
var allR32 = []*myR32{}
我们像这样初始化/填充它们:
func init() {
for script, rt := range unicode.Scripts {
for _, r16 := range rt.R16 {
allR16 = append(allR16, &myR16{r16, script})
}
for _, r32 := range rt.R32 {
allR32 = append(allR32, &myR32{r32, script})
}
}
// sort
sort.Slice(allR16, func(i int, j int) bool {
return allR16[i].r16.Lo < allR16[j].r16.Lo
})
sort.Slice(allR32, func(i int, j int) bool {
return allR32[i].r32.Lo < allR32[j].r32.Lo
})
}
最后在排序范围切片中搜索:
func script(r rune) string {
// binary search over ranges
if r <= 0xffff {
r16 := uint16(r)
i := sort.Search(len(allR16), func(i int) bool {
return allR16[i].r16.Hi >= r16
})
if i < len(allR16) && allR16[i].r16.Lo <= r16 && r16 <= allR16[i].r16.Hi {
return allR16[i].script
}
}
r32 := uint32(r)
i := sort.Search(len(allR32), func(i int) bool {
return allR32[i].r32.Hi >= r32
})
if i < len(allR32) && allR32[i].r32.Lo <= r32 && r32 <= allR32[i].r32.Hi {
return allR32[i].script
}
return ""
}
注意:Stride
包中的所有脚本中的unicode
始终为1,我利用了它(并且未将其包含在算法中)。 < / p>
使用相同的代码进行测试,我们得到相同的输出。在Go Playground上尝试。