轨道
/
Julia
Julia
/
练习
/
高中恋人
高中恋人

高中恋人

学习练习

简介

Julia 鼓励程序员尽可能把代码放进能被 JIT 编译的函数里,而且按设计,创建大量小函数本身就是高效的。

这样一来,程序里往往就剩下许多小而简单的函数,而要完成不那么简单的任务,就需要把它们组合起来。

一种显而易见的做法是嵌套调用函数。下面这个例子非常刻意,但能说明问题。

julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

这种做法的缺点在于,嵌套一深,可读性就会迅速下降。

我们需要一种更简单、更灵活的做法。

组合

这是数学家们钟爱的技巧,Julia 也照搬了数学里的写法。

任意数量的函数都可以用 ∘ 运算符composed在一起(输入 \circ 再按 Tab 键)。结果可以当作一个函数来使用。

julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])

有几点需要注意:

  • 起始函数的顺序与嵌套时相同,执行顺序则是从右到左。
  • 组合时不太方便使用广播,但可以在调用组合出来的函数时使用广播。

管道

另一种思路可以看作是_程序员_的做法,而不是_数学家_的做法。

Pipelines在 Unix shell 脚本里已经用了很久,近几年又在主流编程语言中流行起来(有时人们认为 F# 是最早采用这种写法的语言)。

基本思路是:先有一份数据,然后让它依次经过一串函数,从而得到结果。

管道运算符是 |>(F# 和较新版本的 R 都是这样),不过 Julia 还有一个广播版本 .|>。

julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

现在执行顺序严格从左到右,每个函数的输出沿着箭头方向流动,成为下一个函数的输入。

局限、变通方法与其他选择

用来演示组合和管道的函数都只接受_一个_参数,这并非巧合。

有些纯函数式语言会把_第一个_参数送入函数,同时允许再传入其他参数。

相比之下,Julia 的管道里只接受函数的_名字_(或与之等价的东西),不能附带其他参数。

这背后有重要的技术原因(与currying并不是这门语言设计中的标准组成部分有关)。那些完全不了解柯里化的_众多_人,只需接受一点:这个限制并非粗心造成的疏忽,也不太可能在未来的 Julia 版本中改变。

变通方法

我们需要的是只接受单个参数、又能完成所需工作的函数。幸运的是,在 Julia 里定义新函数很容易。

最简单的方式是使用anonymous function。例如,假设有一个输入字符串,我们想按下划线来拆分它:

julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

之后就可以像之前那样,把这个数组传给其他函数。

在这个例子里,把匿名函数括在括号里是可选的,但一般来说,这是减少歧义的好办法。

同样,我们也可以在程序较前面的位置定义一个具名函数,需要时重复使用。

说明

在这个练习中,你将帮助高中情侣在社交媒体上表达爱意,方法是生成一个用两人首字母组成的 Unicode 心形:

❤ J.  +  M. ❤

1. 清理名字

实现cleanupname方法。 它应接受一个名字,把其中所有的-字符替换为空格。 还应该去掉名字开头和结尾的空白。

julia> cleanupname("Jane-Ann")
"Jane Ann"

2. 获取名字的首字母

实现firstletter方法。 它应接受一个名字,并以字符串的形式返回其首字母。 记得复用你在上一步定义的cleanupname,并把它与其他函数组合起来。

julia> firstletter("Jane")
"J"

3. 把首字母格式化为缩写

实现initial方法。 它应接受一个名字,返回其首字母的大写形式,后面跟一个点。 记得复用你在上一步定义的firstletter。

initial("Robert")
"R."

4. 把缩写放进心形里

实现couple方法。 它应接受两个名字,返回两边带有爱心表情的缩写。 记得复用你在上一步定义的initial。

couple("Blake Miller", "Riley Lewis")
"❤ B.  +  R. ❤"
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 高中恋人 了吗?

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