字符👩👩👧👦(有两个女人,一个女孩和一个男孩的家庭)编码如下:
USV gc sc bc age name
U+1F469 So Zyyy ON 6.0 [WOMAN](https://emojipedia.org/emoji/%F0%9F%91%A9/)
U+200D Cf Zinh BN 1.1 [ZERO WIDTH JOINER](https://en.wikipedia.org/wiki/Zero-width_joiner)
U+1F469 So Zyyy ON 6.0 WOMAN
U+200D Cf Zinh BN 1.1 ZERO WIDTH JOINER
U+1F467 So Zyyy ON 6.0 [GIRL](https://emojipedia.org/emoji/%F0%9F%91%A7/)
U+200D Cf Zinh BN 1.1 ZERO WIDTH JOINER
U+1F466 So Zyyy ON 6.0 [BOY](https://emojipedia.org/emoji/%F0%9F%91%A6/)
所以它的编码很有趣单元测试的完美目标。然而,斯威夫特似乎不知道如何对待它。我的意思是
"👩👩👧👦".contains("👩👩👧👦") // true
"👩👩👧👦".contains("👩") // false
"👩👩👧👦".contains("\u{200D}") // false
"👩👩👧👦".contains("👧") // false
"👩👩👧👦".contains("👦") // true
《易经》云:“君子之道,焉可诬也?有始有卒者,其惟圣人乎。)。但它接着说,它不包含一个女人,女孩,或零宽度细木工。这里发生了什么?为什么斯威夫特知道里面有个男孩而不是女人或女孩?**我可以理解,如果它把它作为一个单一的字符,只承认它包含自己,但事实上,它有一个子组件,没有其他人困惑我。
如果我使用类似"👩".characters.first!
的东西,这不会改变。
更令人困惑的是:
let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["👩", "👩", "👧", "👦"]
即使我把ZWJ放在那里,它们也不会反映在字符数组中。接下来发生的事情很能说明问题:
manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true
所以我得到了同样的行为与字符数组…这非常烦人,因为我知道数组是什么样子的。
如果我使用类似"👩".characters.first!
的东西,这也不会改变。
6条答案
按热度按时间hs1ihplo1#
这与
String
类型在Swift中的工作方式以及contains(_:)
方法的工作方式有关。'👩👩👧👦'就是所谓的表情符号序列,它被渲染为字符串中的一个可见字符。序列由
Character
对象组成,同时它由UnicodeScalar
对象组成。如果你检查字符串的字符数,你会看到它是由四个字符组成的,而如果你检查unicode标量计数,它会给你一个不同的结果:
现在,如果你解析字符并打印它们,你会看到看起来像普通字符的东西,但实际上前三个字符在它们的
UnicodeScalarView
中包含一个emoji和一个零宽度的joiner:正如您所看到的,只有最后一个字符不包含零宽度连接符,因此当使用
contains(_:)
方法时,它会按照您所期望的那样工作。由于您没有与包含零宽度连接符的表情符号进行比较,因此该方法不会找到除最后一个字符之外的任何匹配字符。为了扩展这一点,如果您创建一个由以零宽度joiner结尾的emoji字符组成的
String
,并将其传递给contains(_:)
方法,它也将计算为false
。这与contains(_:)
完全相同,range(of:) != nil
试图找到与给定参数的精确匹配。由于以零宽度joiner结尾的字符形成不完整的序列,因此该方法尝试找到参数的匹配,同时将以零宽度joiner结尾的字符组合成完整的序列。这意味着该方法在以下情况下永远不会找到匹配:1.参数以零宽度连接器结束,并且
1.要解析的字符串不包含不完整的序列(即以零宽度连接符结尾并且后面不跟随兼容字符)。
演示:
但是,由于比较只向前看,因此您可以通过向后操作在字符串中找到其他几个完整的序列:
最简单的解决方案是为
range(of:options:range:locale:)
方法提供一个特定的比较选项。选项String.CompareOptions.literal
对 * 精确的逐个字符等价 * 执行比较。顺便说一下,这里的字符是指不是SwiftCharacter
,而是示例和比较字符串的UTF-16表示-然而,由于String
不允许错误的UTF-16,这本质上等同于比较Unicode标量表示。这里我重载了
Foundation
方法,所以如果你需要原始的方法,可以重命名这个方法:现在,该方法对每个字符都“应该”起作用,即使是不完整的序列:
tp5buhyn2#
第一个问题是你正在用
contains
(Swift的String
不是Collection
)桥接到Foundation,所以这是NSString
的行为,我不相信它能像Swift那样强大地处理合成的Emoji。也就是说,我相信Swift现在正在实现Unicode 8,在Unicode 10中也需要围绕这种情况进行修改(所以当他们实现Unicode 10时,这一切都可能改变;我不知道它是否会或不会)。为了简化,让我们摆脱Foundation,使用Swift,它提供了更显式的视图。我们从人物开始:
好的。这正是我们所期望的。但这是个谎言让我们看看这些角色到底是什么。
啊...所以是
["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]
。这让一切都变得更加清晰。不是👩此列表的成员(它是“👩ZWJ”),但👦却是成员。问题是
Character
是一个“字素簇”,它把东西组合在一起(比如附加ZWJ)。您真正要搜索的是一个unicode标量。这完全符合您的预期:当然,我们也可以在这里找到实际的角色:
(This与本·莱杰罗的观点完全吻合我在注意到他回复之前就贴了这个。离开,以防对任何人都更清楚。)
oknwwptz3#
Swift似乎认为
ZWJ
是一个扩展的字素簇,其前面紧跟着一个字符。我们可以在将字符数组Map到它们的unicodeScalars
时看到这一点:这将从LLDB打印以下内容:
此外,
.contains
将扩展的字素簇分组为单个字符。例如,取朝鲜文字符ᄒ
、ᅡ
和ᆫ
(它们组合在一起构成韩语单词“one”:한
):这找不到
ᄒ
,因为三个代码点被分组为一个簇,作为一个字符。类似地,\u{1F469}\u{200D}
(WOMAN
ZWJ
)是一个簇,它充当一个字符。s1ag04yj4#
Swift 4.0更新
String在Swift 4更新中收到了很多修订,如SE-0163中所述。这个演示使用了两个表情符号,代表了两种不同的结构。两者都与一系列emoji相结合。
👍🏽
是两个表情符号👍
和🏽
的组合👩👩👧👦
是四个emoji的组合,零宽度连接器连接。格式为👩joiner👩joiner👧joiner👦
1.数量
在Swift 4.0中,emoji被视为字形簇。每个emoji都被视为1。
count
属性也可直接用于字符串。所以你可以这样直接调用。在Swift 4.0中,字符串的字符数组也被算作字素簇,所以下面的代码都打印1。这两个表情符号是表情符号序列的示例,其中几个表情符号组合在一起,在它们之间具有或不具有零宽度连接符
\u{200d}
。在Swift 3.0中,这样的字符串的字符数组将每个表情符号分开,并导致具有多个元素的数组(表情符号)。在此过程中将忽略连接器。然而,在Swift 4.0中,字符数组将所有emoji视为一个整体。所以任何emoji的值都是1。unicodeScalars
在Swift 4中保持不变。它提供给定字符串中唯一的Unicode字符。2.包含
在Swift 4.0中,
contains
方法忽略了emoji中的零宽度joiner。因此,"👩👩👧👦"
的四个emoji组件中的任何一个都返回true,如果检查joiner,则返回false。然而,在Swift 3.0中,joiner并没有被忽略,而是与它前面的emoji组合在一起。因此,当您检查"👩👩👧👦"
是否包含前三个组成emoji时,结果将为falseoymdgrw75#
其他答案讨论了Swift做了什么,但没有详细说明为什么。
你希望A等于Å吗我想你会的
其中一个是带有组合符的字母,另一个是单个组合字符。你可以给一个基本字符添加许多不同的组合器,而人类仍然会认为它是一个单一的字符。为了处理这种差异,创建了字素的概念来表示人类认为字符的内容,而不管使用的码点是什么。
现在,短信服务已经将字符组合成图形表情符号多年
:)
→🙂
。因此,各种emoji被添加到Unicode中。这些服务也开始将emoji组合成复合emoji。
当然,没有合理的方法将所有可能的组合编码到单独的码点中,因此Unicode联盟决定扩展字素的概念,以包含这些复合字符。
这归结为
"👩👩👧👦"
应该被认为是一个单一的“字素集群”,如果你试图在字素级别使用它,就像Swift默认的那样。如果你想检查它是否包含
"👦"
作为其中的一部分,那么你应该去一个较低的级别。我不知道Swift语法,所以这里有一些Perl 6,它对Unicode的支持水平相似。
(Perl 6支持Unicode版本9,因此可能存在差异)
我们再往下一层
但是,走到这一步可能会让一些事情变得更难。
我假设Swift中的
.contains
使这变得更容易,但这并不意味着没有其他事情变得更困难。例如,在这个级别上工作可以更容易地在复合字符的中间意外拆分字符串。
你不经意间问的是,为什么这个更高层次的表示不像一个更低层次的表示那样工作。答案当然是不应该的。
如果你问自己“为什么这必须如此复杂”,答案当然是“人类”。
vqlkdk9b6#
表情符号,就像unicode标准一样,看似复杂。肤色、性别、工作、人群、零宽度连接器序列、标志(2个字符的unicode)和其他复杂因素都会使表情符号解析变得混乱。一棵圣诞树、一片比萨饼或一堆便便都可以用一个Unicode码位表示。更不用说,当新的表情符号被引入时,iOS支持和表情符号发布之间存在延迟。事实上,不同版本的iOS支持不同版本的Unicode标准。
**TL; DR.**我致力于这些功能,并开源了一个库,我是JKEmoji的作者,以帮助解析带有表情符号的字符串。它使解析变得简单:
5
它通过定期刷新最新Unicode版本(最近的12.0)的所有已识别emoji的本地数据库,并通过查看未识别emoji字符的位图表示,将它们与运行的操作系统版本中识别为有效emoji的内容进行交叉引用。
备注
以前的一个答案被删除,因为我的图书馆做广告,没有明确说明我是作者。我再次承认这一点。