我有一个可以包含任意Unicode字符的字符串,我想获得该字符串的前缀,其UTF-8编码长度尽可能接近32字节,同时仍然是有效的UTF-8,并且不改变字符的含义(即不切断扩展的字形簇)。
请考虑以下正确的示例:
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
下面是一个错误实现的示例:
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
有什么优雅的方法可以做到这一点?(除了试错法之外)
4条答案
按热度按时间qnakjoqk1#
你没有尝试解决问题,SO通常不会为你写代码,所以这里有一些算法建议:
有什么优雅的方法可以做到这一点?(除了试错法之外)
什么是“优雅”?(就像美取决于旁观者的眼光一样......)
从
String.makeIterator
开始,写一个while
循环,只要字节数≤ 32,就在前缀后面加上Character
s。这是一个非常简单的循环,最坏的情况是32次迭代和32次追加。
您可以根据
String
中每个Character
的 * 平均 * 字节长度并使用String.Prefix(Int)
来实现策略。例如,在第一个示例中,字符数为2,字节数为36,平均为18字节/字符,18只进入32一次(我们不处理小数字符或字节!)所以从
Prefix(1)
开始,其具有28的字节计数并且留下1个字符和8个字节-所以剩余部分具有8的平均字节长度并且您正在寻找最多4个字节,8除以4的零次,你就完成了。上面的例子显示了扩展(或不扩展)前缀猜测的情况。如果前缀猜测太长,可以使用前缀字符和字节计数而不是原始字符串的计数从头开始算法。
如果你在实现算法时遇到困难,可以提出一个新的问题,展示你所写的代码,描述问题,毫无疑问,有人会帮助你完成下一步。
高温
kcrjzv8t2#
我发现
String
和String.UTF8View
共享相同的索引,所以我设法创建了一个非常简单(和有效?)的解决方案,我认为:解释(假设
maxLength == 32
和startIndex == 0
):第一种情况(
utf8.count <= maxLength
)应该很清楚,这是不需要工作的地方。对于第二种情况,我们首先得到utf8索引
33
,它可以是因此,如果我们现在将我们的索引向后移动一个字符(具有
formIndex(before:)
),则这将跳到index
之前的第一扩展的文法簇边界,其在情况A和B中是该字符之前的一个字符,并且在C中是该字符的开始。在任何情况下,utf8索引现在将被保证至多为
32
,并且在扩展的字素簇边界处,因此prefix(upTo: index)
将安全地创建长度≤32的前缀。“但并不完美"
理论上,这也应该是最优的解决方案,即前缀的
count
尽可能地接近maxLength
,但有时当字符串以包含多个Unicode标量的扩展字素簇结尾时,formIndex(before: &index)
会比需要的多后退一个字符,因此前缀结束时会更短。我不太清楚为什么会这样。一个不那么优雅但完全“正确”的解决方案是这样的(仍然只有O(n)):
xqk2d5yq3#
我喜欢你提出的第一个解决方案。我发现如果去掉
formIndex
,它会更正确(也更简单)地工作:9nvpjoqh4#
我的解决方案如下所示:
它取可能的最大utf8索引,使用
Index.samePosition(in:)
方法检查它是否是有效的字符索引。如果不是,它逐个减少utf8索引,直到找到有效的字符索引。优点是你可以用utf16代替utf8,它也会起作用。