我有一个可以包含任意 Unicode 字符的字符串,我想得到一个prefix该字符串的UTF-8编码长度尽可能接近32字节,同时仍然是有效的 UTF-8 并且不改变字符的含义(即不切断扩展字素簇)。
考虑一下这个CORRECT例子:
let string = "\u{1F3F4}\u{E0067}\u{E0062}\u{E0073}\u{E0063}\u{E0074}\u{E007F}\u{1F1EA}\u{1F1FA}"
print(string) // ????????????????????????????????????
print(string.count) // 2
print(string.utf8.count) // 36
let prefix = string.utf8Prefix(32) // <-- function I want to implement
print(prefix) // ????????????????????????????
print(prefix.count) // 1
print(prefix.utf8.count) // 28
print(string.hasPrefix(prefix)) // true
这个例子是WRONG执行:
let string = "ar\u{1F3F4}\u{200D}\u{2620}\u{FE0F}\u{1F3F4}\u{200D}\u{2620}\u{FE0F}\u{1F3F4}\u{200D}\u{2620}\u{FE0F}"
print(string) // ar????☠️????☠️????☠️
print(string.count) // 5
print(string.utf8.count) // 41
let prefix = string.wrongUTF8Prefix(32) // <-- wrong implementation
print(prefix) // ar????☠️????☠️????
print(prefix.count) // 5
print(prefix.utf8.count) // 32
print(string.hasPrefix(prefix)) // false
有什么优雅的方法可以做到这一点? (除了反复试验)
我发现String
and String.UTF8View
共享相同的索引,因此我设法创建一个非常简单(且高效?)的解决方案,我认为:
extension String {
func utf8Prefix(_ maxLength: Int) -> Substring {
if self.utf8.count <= maxLength {
return Substring(self)
}
var index = self.utf8.index(self.startIndex, offsetBy: maxLength+1)
self.formIndex(before: &index)
return self.prefix(upTo: index)
}
}
解释(假设maxLength == 32
and startIndex == 0
):
第一种情况(utf8.count <= maxLength
)应该很清楚,那就是不需要工作的地方。
对于第二种情况,我们首先获取 utf8-index33
,这是
- A:字符串的 endIndex(如果它的长度正好是 33 个字节),
- B:字符开头的索引(前一个字符的 33 个字节之后)
- C:字符中间某处的索引(在
因此,如果我们现在将索引向后移动一个字符(使用formIndex(before:)
)这将跳转到之前的第一个扩展字素簇边界index
如果 A 和 B 是该字符之前的一个字符,而在 C 中则是该字符的开头。
无论如何,现在将保证 utf8 索引最多为32
并且在扩展的字素簇边界处,所以prefix(upTo: index)
将安全地创建长度≤32的前缀。
……但这并不完美。
理论上这也应该始终是最佳解决方案,即前缀的count
尽可能接近maxLength
但有时当字符串以由多个 Unicode 标量组成的扩展字素簇结尾时,formIndex(before: &index)
返回的字符比所需的字符多,因此前缀会变短。我不太清楚为什么会这样。
编辑:一个不太优雅但完全“正确”的解决方案是这样的(仍然只有 O(n)):
extension String {
func utf8Prefix(_ maxLength: Int) -> Substring {
if self.utf8.count <= maxLength {
return Substring(self)
}
let endIndex = self.utf8.index(self.startIndex, offsetBy: maxLength)
var index = self.startIndex
while index <= endIndex {
self.formIndex(after: &index)
}
self.formIndex(before: &index)
return self.prefix(upTo: index)
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)