在 golang 中正确拆分符文
Splitting a rune correctly in golang
我想知道是否有一种简单的方法,例如众所周知的函数来处理代码 points/runes,从符文切片的中间取出一块而不弄乱它,或者如果它是全部需要自己编码以减少到等于或小于最大字节数。
具体来说,我想要做的是将字符串传递给函数,将其转换为符文,以便我可以遵守代码点,如果切片长于某些最大字节,则从中心删除足够的符文符文将字节减少到必要的程度。
如果字符串只是单字节字符并且处理如下:
func shortenStringIDToMaxLength(in string, maxLen int) string {
if len(in) > maxLen {
excess := len(in) - maxLen
start := maxLen/2 - excess/2
return in[:start] + in[start+excess:]
}
return in
}
但在可变字符宽度的字节字符串中,要么需要更多的编码循环,要么会有很好的函数来简化此过程。有没有人有关于如何用符文最好地处理这种事情的代码示例?
这里的想法是,字符串将进入的 DB 字段具有固定的最大字节长度,而不是代码点,因此需要一些从符文到最大字节的算法。之所以从字符串中间开始取字符,正是因为这个特定程序的需要。
谢谢!
编辑:
一旦我发现范围运算符尊重字符串上的符文,这就变得很容易只用我发现的字符串来做,因为下面有很好的答案。在这种情况下,我不必担心字符串是格式正确的 UTF 格式,但如果我知道的话,我现在知道 UTF 模块了,谢谢!
这是我最终得到的结果:
package main
import (
"fmt"
)
func ShortenStringIDToMaxLength(in string, maxLen int) string {
if maxLen < 1 {
// Panic/log whatever is your error system of choice.
}
bytes := len(in)
if bytes > maxLen {
excess := bytes - maxLen
lPos := bytes/2 - excess/2
lastPos := 0
for pos, _ := range in {
if pos > lPos {
lPos = lastPos
break
}
lastPos = pos
}
rPos := lPos + excess
for pos, _ := range in[lPos:] {
if pos >= excess {
rPos = pos
break
}
}
return in[:lPos] + in[lPos+rPos:]
}
return in
}
func main() {
out := ShortenStringIDToMaxLength(`123456789 123456789`, 5)
fmt.Println(out, len(out))
}
这是对您的算法的改编,它从前缀的开头和后缀的结尾删除了不完整的符文:
func TrimLastIncompleteRune(s string) string {
l := len(s)
for i := 1; i <= l; i++ {
suff := s[l-i : l]
// repeatedly try to decode a rune from the last bytes in string
r, cnt := utf8.DecodeRuneInString(suff)
if r == utf8.RuneError {
continue
}
// if success : return the substring which contains
// this succesfully decoded rune
lgth := l - i + cnt
return s[:lgth]
}
return ""
}
func TrimFirstIncompleteRune(s string) string {
// repeatedly try to decode a rune from the beginning
for i := 0; i < len(s); i++ {
if r, _ := utf8.DecodeRuneInString(s[i:]); r != utf8.RuneError {
// if success : return
return s[i:]
}
}
return ""
}
func shortenStringIDToMaxLength(in string, maxLen int) string {
if len(in) > maxLen {
firstHalf := maxLen / 2
secondHalf := len(in) - (maxLen - firstHalf)
prefix := TrimLastIncompleteRune(in[:firstHalf])
suffix := TrimFirstIncompleteRune(in[secondHalf:])
return prefix + suffix
}
return in
}
此算法仅尝试从选定的前缀和后缀中删除更多字节。
如果事实证明你需要从后缀中删除 3 个字节以获得一个有效的符文,例如,它不会尝试查看是否可以向前缀添加 3 个字节,以获得最终结果接近 maxLen
字节。
您可以使用简单的算法来查找 start
和 end
,使得字符串 s[:start]
+ s[end:]
比您的字节限制短。但是你需要确保 start
和 end
都是任何 utf-8 序列的第一个字节以保持序列有效。
UTF-8 具有 属性 任何给定字节都是序列的第一个字节,只要它的前两位不是 10。
所以你可以写这样的代码(游乐场:https://play.golang.org/p/xk_Yo_1wTYc)
package main
import (
"fmt"
)
func truncString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
start := (maxLen + 1) / 2
for start > 0 && s[start]>>6 == 0b10 {
start--
}
end := len(s) - (maxLen - start)
for end < len(s) && s[end]>>6 == 0b10 {
end++
}
return s[:start] + s[end:]
}
func main() {
fmt.Println(truncString("this is a test", 5))
fmt.Println(truncString("日本語", 7))
}
此代码具有理想的 属性 需要 O(maxLen) 的时间,无论输入字符串有多长(假设它是有效的 utf-8)。
我想知道是否有一种简单的方法,例如众所周知的函数来处理代码 points/runes,从符文切片的中间取出一块而不弄乱它,或者如果它是全部需要自己编码以减少到等于或小于最大字节数。
具体来说,我想要做的是将字符串传递给函数,将其转换为符文,以便我可以遵守代码点,如果切片长于某些最大字节,则从中心删除足够的符文符文将字节减少到必要的程度。
如果字符串只是单字节字符并且处理如下:
func shortenStringIDToMaxLength(in string, maxLen int) string {
if len(in) > maxLen {
excess := len(in) - maxLen
start := maxLen/2 - excess/2
return in[:start] + in[start+excess:]
}
return in
}
但在可变字符宽度的字节字符串中,要么需要更多的编码循环,要么会有很好的函数来简化此过程。有没有人有关于如何用符文最好地处理这种事情的代码示例?
这里的想法是,字符串将进入的 DB 字段具有固定的最大字节长度,而不是代码点,因此需要一些从符文到最大字节的算法。之所以从字符串中间开始取字符,正是因为这个特定程序的需要。
谢谢!
编辑:
一旦我发现范围运算符尊重字符串上的符文,这就变得很容易只用我发现的字符串来做,因为下面有很好的答案。在这种情况下,我不必担心字符串是格式正确的 UTF 格式,但如果我知道的话,我现在知道 UTF 模块了,谢谢!
这是我最终得到的结果:
package main
import (
"fmt"
)
func ShortenStringIDToMaxLength(in string, maxLen int) string {
if maxLen < 1 {
// Panic/log whatever is your error system of choice.
}
bytes := len(in)
if bytes > maxLen {
excess := bytes - maxLen
lPos := bytes/2 - excess/2
lastPos := 0
for pos, _ := range in {
if pos > lPos {
lPos = lastPos
break
}
lastPos = pos
}
rPos := lPos + excess
for pos, _ := range in[lPos:] {
if pos >= excess {
rPos = pos
break
}
}
return in[:lPos] + in[lPos+rPos:]
}
return in
}
func main() {
out := ShortenStringIDToMaxLength(`123456789 123456789`, 5)
fmt.Println(out, len(out))
}
这是对您的算法的改编,它从前缀的开头和后缀的结尾删除了不完整的符文:
func TrimLastIncompleteRune(s string) string {
l := len(s)
for i := 1; i <= l; i++ {
suff := s[l-i : l]
// repeatedly try to decode a rune from the last bytes in string
r, cnt := utf8.DecodeRuneInString(suff)
if r == utf8.RuneError {
continue
}
// if success : return the substring which contains
// this succesfully decoded rune
lgth := l - i + cnt
return s[:lgth]
}
return ""
}
func TrimFirstIncompleteRune(s string) string {
// repeatedly try to decode a rune from the beginning
for i := 0; i < len(s); i++ {
if r, _ := utf8.DecodeRuneInString(s[i:]); r != utf8.RuneError {
// if success : return
return s[i:]
}
}
return ""
}
func shortenStringIDToMaxLength(in string, maxLen int) string {
if len(in) > maxLen {
firstHalf := maxLen / 2
secondHalf := len(in) - (maxLen - firstHalf)
prefix := TrimLastIncompleteRune(in[:firstHalf])
suffix := TrimFirstIncompleteRune(in[secondHalf:])
return prefix + suffix
}
return in
}
此算法仅尝试从选定的前缀和后缀中删除更多字节。
如果事实证明你需要从后缀中删除 3 个字节以获得一个有效的符文,例如,它不会尝试查看是否可以向前缀添加 3 个字节,以获得最终结果接近 maxLen
字节。
您可以使用简单的算法来查找 start
和 end
,使得字符串 s[:start]
+ s[end:]
比您的字节限制短。但是你需要确保 start
和 end
都是任何 utf-8 序列的第一个字节以保持序列有效。
UTF-8 具有 属性 任何给定字节都是序列的第一个字节,只要它的前两位不是 10。
所以你可以写这样的代码(游乐场:https://play.golang.org/p/xk_Yo_1wTYc)
package main
import (
"fmt"
)
func truncString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
start := (maxLen + 1) / 2
for start > 0 && s[start]>>6 == 0b10 {
start--
}
end := len(s) - (maxLen - start)
for end < len(s) && s[end]>>6 == 0b10 {
end++
}
return s[:start] + s[end:]
}
func main() {
fmt.Println(truncString("this is a test", 5))
fmt.Println(truncString("日本語", 7))
}
此代码具有理想的 属性 需要 O(maxLen) 的时间,无论输入字符串有多长(假设它是有效的 utf-8)。