热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

为什么表情符号在Swift字符串中被如此奇怪地处理?

如何解决《为什么表情符号在Swift字符串中被如此奇怪地处理?》经验,为你挑选了5个好方法。

角色(有两个女人,一个女孩和一个男孩的家庭)编码如下:

U+1F469 WOMAN,
?U+200D ZWJ,
U+1F469 WOMAN,
U+200D ZWJ,
U+1F467 GIRL,
U+200D ZWJ,
U+1F466 BOY

所以它非常有趣地编码; 单元测试的完美目标.然而,斯威夫特似乎不知道如何对待它.这就是我的意思:

"???".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!.



1> xoudini..:

这与StringSwift中的类型如何工作以及该 contains(_:)方法的工作方式有关.

''是所谓的表情符号序列,它被渲染为字符串中的一个可见字符.序列由Character对象组成,同时它由UnicodeScalar对象组成.

如果检查字符串的字符数,您将看到它由四个字符组成,而如果您检查unicode标量计数,它将显示不同的结果:

print("???".characters.count)     // 4
print("???".unicodeScalars.count) // 7

现在,如果你解析字符并打印它们,你会看到看起来像普通字符的东西,但实际上这三个第一个字符包含一个表情符号以及一个零宽度的连接符UnicodeScalarView:

for char in "???".characters {
    print(char)

    let scalars = String(char).unicodeScalars.map({ String($0.value, radix: 16) })
    print(scalars)
}

// ?
// ["1f469", "200d"]
// ?
// ["1f469", "200d"]
// ?
// ["1f467", "200d"]
// 
// ["1f466"]

如您所见,只有最后一个字符不包含零宽度连接符,因此在使用该contains(_:)方法时,它可以按预期工作.由于您没有与包含零宽度连接符的表情符号进行比较,因此该方法不会找到除最后一个字符之外的任何字符的匹配项.

为了对此进行扩展,如果您创建一个String由以零宽度连接符结尾的表情符号字符组成,并将其传递给contains(_:)方法,它也将评估为false.这与contains(_:)完全相同range(of:) != nil,它试图找到与给定参数的精确匹配.由于以零宽度连接符结尾的字符形成不完整的序列,因此该方法尝试查找参数的匹配,同时将以零宽度连接符结尾的字符组合成完整的序列.这意味着在以下情况下,该方法永远不会找到匹配:

    参数以零宽度连接符结束,并且

    要解析的字符串不包含不完整的序列(即以零宽度连接符结尾,而不是后跟兼容字符).

展示:

let s = "\u{1f469}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}" // ???

s.range(of: "\u{1f469}\u{200d}") != nil                            // false
s.range(of: "\u{1f469}\u{200d}\u{1f469}") != nil                   // false

但是,由于比较只是向前看,您可以通过向后工作在字符串中找到其他几个完整的序列:

s.range(of: "\u{1f466}") != nil                                    // true
s.range(of: "\u{1f467}\u{200d}\u{1f466}") != nil                   // true
s.range(of: "\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}") != nil  // true

// Same as the above:
s.contains("\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}")          // true

最简单的解决方案是为方法提供特定的比较选项range(of:options:range:locale:).该选项String.CompareOptions.literal精确的逐个字符等效性执行比较.作为旁注,这里的字符意思不是 Swift Character,而是实例和比较字符串的UTF-16表示 - 但是,由于String不允许格式错误的UTF-16,这基本上等同于比较Unicode标量表示.

在这里我重载了这个Foundation方法,所以如果你需要原始的方法,重命名这个或那个:

extension String {
    func contains(_ string: String) -> Bool {
        return self.range(of: string, options: String.CompareOptions.literal) != nil
    }
}

现在,该方法与每个字符"应该"一起工作,即使序列不完整:

s.contains("")          // true
s.contains("\u{200d}")  // true
s.contains("\u{200d}")    // true


@MartinR根据当前的UTR29(Unicode 9.0),它**是一个扩展的字形集群([规则GB10和GB11](http://unicode.org/reports/tr29/#GB10)),但是Swift清楚使用旧版本.显然[修复这是该语言版本4的目标](https://github.com/apple/swift/blob/master/docs/StringManifesto.md#unicode-9-conformance),所以此行为将在未来.
@MichaelHomer:显然已经修复了,".".count`使用当前的Xcode 9 beta和Swift 4评估为"1".
哇。太好了 但是现在我回想起过去的日子,那时我遇到的最糟糕的问题是字符串是否使用C或Pascal样式编码。

2> Rob Napier..:

第一个问题是你正在与基金会建立联系contains(Swift String不是a Collection),所以这就是NSString行为,我不相信把握Emoji就像Swift一样强大.也就是说,Swift我认为现在正在实施Unicode 8,这也需要在Unicode 10中围绕这种情况进行修改(因此,当它们实现Unicode 10时,这可能都会发生变化;我还没有考虑是否会这样做).

为简化起见,让我们摆脱Foundation,并使用Swift,它提供更明确的视图.我们将从角色开始:

"???".characters.forEach { print($0) }
?
?
?

好.这就是我们的预期.但这是谎言.让我们看看这些角色究竟是什么.

"???".characters.forEach { print(String($0).unicodeScalars.map{$0}) }
["\u{0001F469}", "\u{200D}"]
["\u{0001F469}", "\u{200D}"]
["\u{0001F467}", "\u{200D}"]
["\u{0001F466}"]

啊......就是这样["ZWJ", "ZWJ", "ZWJ", ""].这让一切都变得更加清晰.不是此列表的成员(它是"ZWJ"),但是是成员.

问题在于它Character是一个"字形集群",它将事物组合在一起(如附加ZWJ).你真正寻找的是一个unicode标量.这完全符合您的预期:

"???".unicodeScalars.contains("") // true
"???".unicodeScalars.contains("\u{200D}") // true
"???".unicodeScalars.contains("") // true
"???".unicodeScalars.contains("") // true

当然,我们也可以寻找那里的实际角色:

"???".characters.contains("\u{200D}") // true

(这大大复制了Ben Leggiero的观点.我在注意到他已经回答之前发布了这个.留下以防任何人都清楚.)


零宽度木工

3> Ben Leggiero..:

似乎Swift认为a ZWJ是一个扩展的字形集群,其前面有一个字符.我们可以在将字符数组映射到它们时看到unicodeScalars:

Array(manual.characters).map { $0.description.unicodeScalars }

这将从LLDB打印以下内容:

? 4 elements
  ? 0 : StringUnicodeScalarView("?")
    - 0 : "\u{0001F469}"
    - 1 : "\u{200D}"
  ? 1 : StringUnicodeScalarView("?")
    - 0 : "\u{0001F469}"
    - 1 : "\u{200D}"
  ? 2 : StringUnicodeScalarView("?")
    - 0 : "\u{0001F467}"
    - 1 : "\u{200D}"
  ? 3 : StringUnicodeScalarView("")
    - 0 : "\u{0001F466}"

此外,.contains组将字形集群扩展为单个字符.例如,取出hangul字符?,??(结合起来使韩语单词为"one" :) ???:

"\u{1112}\u{1161}\u{11AB}".contains("\u{1112}") // false

找不到这个,?因为三个代码点被分组为一个作为一个字符的集群.同样,\u{1F469}\u{200D}(WOMAN ZWJ)是一个集群,作为一个字符.



4> Brad Gilbert..:

其他答案讨论了Swift的作用,但没有详细说明原因.

你期望"Å"等于"Å"吗?我希望你会的.

其中一个是带有组合器的字母,另一个是单个组合字符.您可以向基本角色添加许多不同的组合器,而人类仍然会将其视为单个角色.为了处理这种差异,创建了一个字母的概念来表示人类将考虑一个字符而不管使用的代码点.

现在,短信服务已经组合字符成图形表情符号多年:) →  . So various emoji were added to Unicode.
These services also started combining emoji together into composite emoji.
There of course is no reasonable way to encode all possible combinations into individual codepoints, so The Unicode Consortium decided to expand on the concept of graphemes to encompass these composite characters.

What this boils down to is "???"如果你试图在字形级别使用它,那么归结为应该被视为单个"字形集群",正如Swift默认情况下那样.

如果你想检查它是否包含""它的一部分,那么你应该降低到较低的水平.


我不知道Swift语法,所以这里有一些Perl 6,它对Unicode有类似的支持.
(Perl 6支持Unicode版本9,因此可能存在差异)

say "\c[family: woman woman girl boy]" eq "???"; # True

# .contains is a Str method only, in Perl 6
say "???".contains("???")    # True
say "???".contains("");        # False
say "???".contains("\x[200D]");  # False

# comb with no arguments splits a Str into graphemes
my @graphemes = "???".comb;
say @graphemes.elems;                # 1

让我们下降一个级别

# look at it as a list of NFC codepoints
my @components := "???".NFC;
say @components.elems;                     # 7

say @components.grep("".ord).Bool;       # True
say @components.grep("\x[200D]".ord).Bool; # True
say @components.grep(0x200D).Bool;         # True

降低到这个水平会让事情变得更难.

my @match = "???".ords;
my $l = @match.elems;
say @components.rotor( $l => 1-$l ).grep(@match).Bool; # True

我认为.contains在Swift中使这更容易,但这并不意味着没有其他事情变得更加困难.

例如,在此级别工作可以更容易地将字符串意外地拆分到复合字符的中间.


你无意中问的是为什么这种更高级别的表示不像低级表示那样工作.答案当然是,它不应该.

如果你问自己" 为什么这必须如此复杂 ",答案当然是" 人类 ".


你在最后一个例子中失去了我; `rotor`和`grep`在这做什么?什么是`1- $ l`?
术语"石墨烯"至少有50年的历史.Unicode将它引入标准,因为它们已经使用术语"字符"来表示与通常认为的字符完全不同的东西.我可以阅读你所写的与之相符的内容,但怀疑其他人可能会得到错误的印象,因此这个(希望澄清)评论.
@BenLeggiero首先,`转子`.代码`说(1,2,3,4,5,6).rotor(3)`产生`((1 2 3)(4 5 6))`.这是一个列表列表,每个长度为"3".`say(1,2,3,4,5,6).rotor(3 =>​​ - 2)`产生相同的结果,除了第二个子列表以"2"开头而不是"4",第三个以"3"开头,等等,得到`((1 2 3)(2 3 4)(3 4 5)(4 5 6))`.如果`@match`包含`"".ords`那么@Brad的代码只创建一个子列表,所以`=> 1- $ l`位是无关的(未使用).只有当`@ match`比`@ components`短时才有意义.

5> Fangming..:

Swift 4.0更新

字符串在swift 4更新中收到大量修订,如SE-0163中所述.表示两种不同结构的演示使用了两个表情符号.两者都与一系列表情符号相结合.

is the combination of two emoji,

???是四个表情符号的组合,连接零宽度的连接器.格式是?joiner?joiner?joiner

1.计数

在swift 4.0中.表情符号被计为字形簇.每个表情符号都计为1. count属性也可直接用于字符串.所以你可以这样直接调用它.

"".count  // 1. Not available on swift 3
"???".count  // 1. Not available on swift 3

字符串的字符数组也被计为swift 4.0中的字形簇,因此以下两个代码都打印1.这两个表情符号是表情符号序列的示例,其中几个表情符号组合在一起,count它们之间有或没有零宽度连接符.在swift 3.0中,这种字符串的字符数组分隔出每个表情符号,并产生一个包含多个元素(表情符号)的数组.在此过程中忽略了连接器.但是在swift 4.0中,字符数组将所有表情符号视为一个整体.所以任何表情符号都将是1.

"".characters.count  // 1. In swift 3, this prints 2
"???".characters.count  // 1. In swift 3, this prints 4

\u{200d} 在swift 4中保持不变.它在给定的字符串中提供唯一的Unicode字符.

"".unicodeScalars.count  // 2. Combination of two emoji
"???".unicodeScalars.count  // 7. Combination of four emoji with joiner between them

2.包含

在swift 4.0中,unicodeScalars方法忽略了表情符号中的零宽度连接符.因此,对于四个表情符号组件中的任何一个,它都返回true,contains如果检查了连接器,则返回false.但是,在swift 3.0中,连接器不会被忽略,并与前面的表情符号结合使用.因此,当您检查是否"???"包含前三个组件表情符号时,结果将为false

"".contains("")       // true
"".contains("")        // true
"???".contains("???")       // true
"???".contains("")       // true. In swift 3, this prints false
"???".contains("\u{200D}") // false
"???".contains("")       // true. In swift 3, this prints false
"???".contains("")       // true


推荐阅读
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Imdevelopinganappwhichneedstogetmusicfilebystreamingforplayinglive.我正在开发一个应用程序,需要通过流 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • Introduction(简介)Forbeingapowerfulobject-orientedprogramminglanguage,Cisuseda ... [详细]
  • MySQL5.6.40在CentOS764下安装过程 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
author-avatar
咔西咔嘻
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有