Swift的countElements()在计算包含标志表情符号的字符串长度时返回不正确的值

15
let str1 = ""
let str2 = "....."

println("\(countElements(str1)), \(countElements(str2))")

结果:1,10

但是str1难道不应该有5个元素吗?

当我使用旗帜表情时,似乎只发生了这个bug。


1
是的,这很奇怪。我尝试了不同的表情符号,只有国旗表情符号会导致这种情况。即使使用没有空格的不同国旗也会导致这种情况。 - Fogmeister
6
我认为这似乎是个错误。""是由区域指示符号字母D和区域指示符号字母E组成的“扩展字形簇”,并计为一个字符,而实际上应该是5个字符。即使""也只有一个字符计数。也许对Unicode标准有更好了解的人可以解释一下。 - Martin R
2
似乎任意序列的“区域指示符字母”被视为单个字符簇。例如,let str1 = "\u{1F1E6}\u{1F1E7}\u{1F1E8}\u{1F1E9}\u{1F1EA}\u{1F1EB}" 打印出来是 `` 但被计算为单个字符。 - Martin R
有趣的是,str1.startIndex.successor() == str1.endIndex - rintaro
2
我无法理解它,但这里是字形簇边界规范 - rintaro
@rintaro:感谢提供链接。我已添加了[unicode]标签,或许能吸引一些该领域的专家。 - Martin R
2个回答

21

Swift 4更新(Xcode 9)

从Swift 4开始(经过Xcode 9测试),基于Unicode 9标准,每两个区域指示符号后面将会中断字形簇。

let str1 = ""
print(str1.count) // 5
print(Array(str1)) // ["", "", "", "", ""]

同样,String是由其字符组成的集合,因此可以使用str1.count获得字符计数。


(旧版Swift 3及更早版本的答案:)

“3 Grapheme Cluster Boundaries”中 在“标准Annex #29 UNICODE TEXT SEGMENTATION”中: (强调添加):

传统的字形群集被定义为一个基础(例如A或カ) 零个或多个连续字符。可以将其视为形成“堆栈”的字符序列。

基数可以是单个字符,也可以是由Hangul Jamo字符序列组成的任何序列 根据Unicode标准中的D133定义,成为Hangul音节,或是由任何区域指示符(RI)字符序列。 RI字符成对使用以表示对应于ISO国家代码的表情符号国旗符号。超过两个RI字符的序列应分隔其他字符, 例如U+200B ZWSP。

(感谢@ rintaro提供的链接)。

Swift Character表示扩展的字形簇,因此(根据此参考资料)正确的是将任何区域指示符号 序列计为一个字符。

您可以使用零宽度非连接符分隔“标志”:

let str1 = "\u{200C}"
print(str1.characters.count) // 2

或者插入一个零宽空格:

let str2 = "\u{200B}"
print(str2.characters.count) // 3

这也解决了可能存在的歧义,例如“​​​”应该是“​​”还是“​”?

另请参见如何确定两个表情符号是否将显示为一个表情符号? 关于一种可能的方法来计算Swift字符串中“组合字符”的数量,对于您的let str1 = ""将返回5


2
不错的发现!现在知道他们为什么要这样设计会很有趣,我认为这是一个缺陷。 - DarkDust
1
Randy,这不是Swift的错,因为它正确地实现了Unicode标准。Unicode联盟面临的问题是如何确定区域指示符之间的断点。有三个基本选项:寻找第一个指示符并计算每两个(可能很慢);用一个不可见字符将两个指示符粘在一起;或者用一个不可见字符拆分成对的指示符。他们最终选择了第三个选项。 - Martin Winter
2
值得注意的是,从Unicode 9.0.0和Unicode标准公告#29的版本29开始,规则已更改。在区域指示符号序列中,每隔两个区域指示符号后会断开一个字形簇。我不知道Swift是否已经实现了新规则。 - user2357112
1
@Supuhstar:确实如此,感谢您的反馈! - Martin R
1
@user2357112:显然,Swift 4现在实现了新规则(已在Xcode 9 beta中进行测试)。 - Martin R
显示剩余9条评论

4

以下是我为解决这个问题而采用的方法,适用于 Swift 3

let str = "" //or whatever the string of emojis is
let range = str.startIndex..<str.endIndex
var length = 0
str.enumerateSubstrings(in: range, options: NSString.EnumerationOptions.byComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in
        length = length + 1
    }
print("Character Count: \(length)")

这个方法解决了字符计数和表情符号的所有问题,并且是我发现的最简单的方法。

比较一下这个链接中的答案:https://dev59.com/ilkT5IYBdhLWcg3wFrkT#39104563(我在我的回答中提到了它) :) - Martin R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接