在Vectors概念中,我们说过“数组是 Julia 语言的核心”,而向量是一维的。
鉴于此,我们完全可以期待这门语言提供许多灵活而强大的方式,让我们对向量做各种事情,不管这具体意味着什么。
一些非常简单的函数接受向量输入,返回标量输出。
v = [2, 3, 4]
length(v) # => 3
sum(v) # => 9
假设你有一个数值向量,想从每个值中减去 0.5。
julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
1.2
1.5
1.7
julia> v - 0.5
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)
这样会失败,那减去另一个向量呢?
julia> v - [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
成功了,但随着向量越来越长,这种做法相当繁琐,也很占内存。
幸运的是,Julia 有一个“魔法”点号,可以非常简单地解决这个问题:只需要v .- 0.5就够了。
下一节会解释其中的原因。
也就是说,v - 0.5会失败,而v .- 0.5能成功,我们需要弄明白这个点号在做什么。
它做了两件事,二者结合起来才得到想要的结果。
首先,在任何中缀运算符前加点号,意味着“把这个运算分别应用到每个元素上”。
同样,在函数名后面加点号,会把它“向量化”,即使这个函数原本是为标量输入而写的。
julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
1.0
2.0
3.0
在前面的例子中我们看到,可以相减两个长度相等的向量,不过请理解,.-比-更安全,因为它清楚地表明了逐元素的意图。
julia> v .- [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
长度不相等的向量呢?
julia> v .- [0.5, 0.5]
ERROR: DimensionMismatch: arrays could not be broadcast to a common size
julia> v .- [0.5,]
3-element Vector{Float64}:
0.7
1.0
1.2
一般来说,长度不相等就是一个错误,除非其中一个的长度为 1(严格来说,是“单例”维度)。
像[0.5,]或者直接写0.5这样的单例,会自动通过重复扩展到所需的长度。这正是broadcasting的核心。
如何通过下标来选取向量元素,在之前的概念中已经讨论过。
a = collect('A':'Z') # => 26-element Vector{Char}
# index with an integer
a[2] # => 'B'
# index with a range
a[12:2:18] # => ['L', 'N', 'P, 'R']
# index with another vector
a[ [1, 3, 5] ] # => ['A', 'C', 'E']
也可以选取满足某个逻辑表达式(严格来说,是“谓词”)的元素。这通常需要用广播。
julia> a[a .< 'D']
3-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
表达式越复杂,点号就越多(不过它们很小,也很好输入)。
julia> a[a .< 'D' .|| a .> 'W']
6-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)
再提醒一下,“向量”实际上可以是任何合适的有序可迭代对象,比如一个范围:
julia> n = 3:10
3:10
julia> n[isodd.(n)]
4-element Vector{Int64}:
3
5
7
9
你是一位热衷于观鸟的人,会记录过去 7 天里有多少只鸟来过你的花园。
你有 6 个任务,它们都与来过你花园的鸟的数量有关。
实现 today() 函数,返回今天有多少只鸟来过你的花园。
鸟的数量按天排列,第一个元素是最早那天的数量,最后一个元素是今天的数量。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> today(birds_per_day)
1
实现 increment_todays_count() 函数,把今天的计数加一:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> increment_todays_count(birds_per_day)
[2, 5, 0, 7, 4, 2]
实现 has_day_without_birds() 函数,如果存在某一天没有鸟来过花园,就返回 true,否则返回 false:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> has_day_without_birds(birds_per_day)
true
实现 count_for_first_days() 函数,返回从这周开始以来来过你花园的鸟的数量,但只统计从这周开始算起的指定天数。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> count_for_first_days(birds_per_day, 4)
14
有些天比其他天更忙。
繁忙的一天指的是至少有 5 只鸟来过你的花园。
实现 busy_days() 函数,返回繁忙的天数:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> busy_days(birds_per_day)
2
你决定扩展记录,保存多周的数据。 每种情况下,计数都按一周中的天排列,第一个条目是周一,最后一个是周日。
实现 average_per_day() 函数,返回 2 周的平均值。
julia> week1 = [7, 2, 9, 1, 3, 0, 10]
julia> week2 = [2, 6, 4, 1, 3, 8, 9]
julia> average_per_day(week1, week2)
[4.5, 4.0, 6.5, 1.0, 4.0, 3.0, 9.5]