Go:如何找出符文的Unicode属性?

时间:2017-03-27 10:39:54

标签: go unicode

我想找出一个符文的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库中的东西,而且我忽略了。)

全部谢谢!

2 个答案:

答案 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上尝试。