轨道
/
Julia
Julia
/
练习
/
一尘不染
一尘不染

一尘不染

学习练习

简介

这是一个_很大_的话题! 完全可以为它写一本厚厚的书,而且确实有好几个人这么做了(在亚马逊上搜索 “unicode book” 就能看到一些例子)。

极简历史

在早些年,在计算机里处理字符要简单得多,那时程序员认为英语是唯一重要的语言。 于是:26 个字母,分大小写,10 个数字,几个标点符号,再加上一个用来响铃的编码(0x07),全部都能塞进 7 位里:这就是 ASCII 字符集。

很自然地,人们开始问:那 à、ä 和 Ł 怎么办?接着又有人开始问 ऄ、ஹ 和 ญ 怎么办,而年轻人想要 emoji 😱。 那该怎么办呢?

长话短说,许多聪明又有耐心的人在委员会里工作了好几年,制定出 Unicode 字符集以及 UTF-8 这类编码的种种细节,而大量软件都需要经历一次_极其_复杂的改写。 而且,还引入了许多新的 bug。

为了防止_一切_都崩溃,Unicode/UTF-8 的设计保证前 127 个编码与 ASCII 完全一致_(连响铃那个也是)_。

Julia 中的字符

2005 年前后之后设计的语言有一个巨大的优势:那时已经存在一个相当稳定的 Unicode 标准。

Julia(2012 年首次发布)可以假定一切都将使用 Unicode:字符、字符串、变量名和函数名、数学运算符……

引用手册中的话:“Julia 让处理纯 ASCII 文本变得简单高效,而处理 Unicode 也尽可能简单高效。” 注意“尽可能”这几个字,它是这句话的重要部分。

字符字面量用单引号书写,与用双引号书写的字符串是不同的。

这对来自 C/C++ 世界的人来说显而易见,但对 Python 和 JavaScript 程序员来说可能会让人困惑。

julia> a = 'a'  # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

julia> typeof(a)
Char

julia> jha = 'झ'  # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)

julia> typeof(jha)
Char

julia> '❤'  # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)

从这些例子可以看出,类型是 Char,而且 Julia 还提供了关于字符类别的更多信息。

再仔细看,这些字符似乎可以用 4 个十六进制数字表示。 完整的字符集最多需要 6 个十六进制数字。

这些数字叫做“码点”,目前的取值范围从 U+0000 到 U+10FFFF。 它们会在 REPL 中显示出来,但在代码里要用 codepoints() 来获取。

在 Char 和 Int 之间转换很简单:

julia> Int('a')
97

julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

编译器允许对 Char 进行_某些_形式的整数运算:

julia> 'b' - 'a'  # interval, in alphabetic order
1

julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)

julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

julia> 'f' + ('A' - 'a')  # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)

处理字符的函数

许多处理字符串的函数也能作用于 Char 输入。

  • 对适用的字母系统,用 uppercase() 和 lowercase() 改变大小写。
  • 用 isuppercase() 和 islowercase() 检测大小写。
  • 用以下函数检测字符类型:
    • isletter() 涵盖许多字母系统
    • isdigit(),严格检测 0:9
    • isnumeric(),比 isdigit 更宽泛,对 ¾ 和各种非欧洲文字都返回 true
    • isxdigit(),十六进制数字
    • isascii(),Unicode 之前的字符
    • ispunct(),标点符号
    • isspace(),任意空白字符
    • isprint(),可打印字符(相反的是 iscntrl())
islowercase('A')  # false
uppercase('γ')  # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@')  # true
isdigit('A')  # false
isxdigit('A')  # true

Char 向量与字符串的相互转换

要把字符串转成 Char 向量,可以用 collect()。

要把 Char 向量转成字符串,可以用 String() 构造函数。

julia> s = "abcde"
"abcde"

julia> cv = collect(s)
5-element Vector{Char}:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)

julia> String(cv)
"abcde"

这对任何字符都适用,不只是 ASCII。

julia> collect("❤,😱")
3-element Vector{Char}:
 '❤': Unicode U+2764 (category So: Symbol, other)
 ',': ASCII/Unicode U+002C (category Po: Punctuation, other)
 '😱': Unicode U+1F631 (category So: Symbol, other)

注意,String() 构造函数作用于向量。 要把单个 Char _转换_成只有一个字符的字符串,用的函数是 string(),注意是小写的 s。

julia> string('a')
"a"

存储

本文到目前为止的内容看起来都比较简单,那真的没什么好担心的吗?

可惜,这也太乐观了!

一个麻烦来自每个码点“最多”需要 6 个十六进制数字。 这意味着在 UTF-8 编码下,不同字符在内存中占用的空间也不同。

一个字节只能存储最大到 255 的(无符号)数字,也就是两个十六进制数字,所以 UTF-8 用可变数量的字节(1 到 4 个)来存储一个 Char。 这些字节叫做“码元”,ncodeunits() 函数会返回给定字符所需的码元数。

julia> codepoint(jha)  # jha 'झ' is defined in an earlier example
0x0000091d

julia> ncodeunits(jha)
3

julia> ncodeunits('a')  # ASCII character
1

julia> ncodeunits('😱')  # emoji
4

另外,并不是所有能在屏幕上显示的东西都有自己唯一的码点。 有些视觉上不同的字符被认为是由其他字符派生出来的,所以 Unicode 把它们当作一个父字符加上一个修饰符。

这个问题会影响字符串,给下标操作带来挑战。

说明

在本练习中,你将实现一组实用例程中的一部分,帮助开发者清理标识符名称。

在这 6 个任务中,你将逐步构建 transform(转换单个字符)和 clean(转换字符串)这两个函数。

合法的标识符由零个或多个字母、下划线、连字符、问号和 emoji 组成。

如果向 clean 函数传入空字符串,则应返回空字符串。

1. 将遇到的所有连字符替换为下划线

实现 transform 函数,将任何连字符替换为下划线。

julia> transform('-')
"_"

2. 移除所有空白字符

移除所有空白字符,包括开头和结尾的空白字符。

julia> transform(' ')
""

3. 将 camelCase 转换为 kebab-case

修改 transform 函数,将 camelCase 转换为 kebab-case。

julia> transform('D')
"-d"

4. 省略数字字符

修改 transform 函数,省略所有数字字符。

julia> transform('7')
""

5. 将希腊小写字母替换为问号

修改 transform 函数,替换 'α' 到 'ω' 范围内的所有希腊字母。

julia> transform('β')
"?"

6. 组合这些操作以处理字符串

实现 clean 函数,将这些操作应用到一个完整的字符串上。

不符合上述规则的字符应原样保留。

julia> clean("  a2b Cd-ω😀  ")
"ab-cd_?😀"
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 一尘不染 了吗?

注册 Exercism,借助 35 个概念128 个练习 和真人导师指导,学习并掌握 Julia,全部免费。