我想找出一个符文的Unicode属性,特别是它的script属性的值。统一码有这样的说法(在http://www.unicode.org/reports/tr24/第1.5节):
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.Go的unicode包为我提供了一种问“这个符文在脚本x中吗?”的方法,但是我没有办法问“这个符文在什么脚本中?”。我显然可以遍历所有的脚本,但那将是浪费的。有没有更聪明的方法来找出符文的脚本?(我总是可以实现一个自组织列表,但是我在标准go库中寻找一些我已经做了我想要的,但我忽略了的东西。)
谢谢大家!
发布于 2017-03-28 00:07:15
最简单、最快速的解决方案是编写函数。例如,
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
$ 发布于 2017-03-28 07:04:52
改进PeterSO的答案
PeterSO的回答很好,很清楚。但是它在内存使用上并不容易,因为它在一个映射中存储了超过十万个条目,值是string类型的。即使一个string值只是一个存储指针和长度的头(参见reflect.StringHeader),在一个映射中拥有如此多的指针和长度仍然是多MB的(比如6MB)!
由于可能的不同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将导致大约2MB的内存使用,并且需要“一些”时间来构建这个映射,这可能是可以接受的,也可能是不可接受的。
还有另一种方法/解决方案。我们可以选择不构建“所有”符文的映射,而只存储所有范围的切片(实际上是2个范围切片,一个具有16位Unicode值,另一个具有32位Unicode代码点)。
这样做的好处是,射程的数量远远少于符文的数量:只有852个(与100,000+符文相比)。与解决方案#1相比,总共具有852个元素的2个切片的存储器使用将是可以忽略的。
在我们的ranges中,我们还存储了脚本(name),因此我们可以返回此信息。我们也可以只存储一个名称索引(如解决方案#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 ""
}注意:在unicode包的所有脚本中,Stride始终为1,我利用了这一点(并且没有将其包括在算法中)。
使用相同的代码进行测试,我们会得到相同的输出。在Go Playground上试试。
https://stackoverflow.com/questions/43044164
复制相似问题