9.2 数组的表示
我们在很早以前就已经见过数组值的一般表示法了。它是这样的:
julia> [1, 2, 3, 4, 5]
5-element Array{Int64,1}:
1
2
3
4
5
julia>
我们阅读 REPL 环境回显的内容就可以知道,[1, 2, 3, 4, 5]
表示了一个有 5 个元素的一维数组,且元素的类型是Int64
。不过,你可能会有个疑惑,为什么回显内容中的元素值是竖排展示的呢?
实际上,这就是一维数组的正常形状。它是一个由多个值组成的纵队,相当于表格中的一列。从线性代数的角度讲,这叫做列向量。更宽泛地说,只要我们用英文逗号分隔数组中的多个元素值,就会得到一个(列)向量。除了英文逗号,我们还可以使用英文分号:
julia> [1; 2; 3; 4; 5]
5-element Array{Int64,1}:
1
2
3
4
5
julia>
在这里,我们可以认为这两种表示法是等价的。但我还是建议你在一般情况下使用英文逗号,因为英文分号在含义上还是有别于英文逗号的。在数组值字面量的上下文中,英文分号代表着拼接。它可以把相关的数组中的所有元素值以及单一值全都拼接在一起,从而产生一个新的数组。例如:
julia> [[1]; [2,3]; 4; 5]
5-element Array{Int64,1}:
1
2
3
4
5
julia>
请注意,这里被英文分号分隔的不仅有 2 个单一值,还有 2 个数组。不过这些数组都被拆解了,其中的元素值也都成为了新数组的元素值。这就是拼接的作用。我们还需要注意,正是由于这两个符号在含义上的不同,所以我们不能在同一个地方混用它们。
我们现在来试验一下,把上述示例中的英文分号全都替换成英文逗号会产生怎样的效果:
julia> [[1], [2,3], 4, 5]
4-element Array{Any,1}:
[1]
[2, 3]
4
5
julia>
可以看到,英文逗号并不会使相邻的数组被拆解。这些数组都被识别成了单一的元素值。因此,上述数组的元素类型才是Any
,而不是Int64
。更明确地说,只要一个数组值字面量包含了不同类型的元素值,Julia 就会试图找到它们的公共类型,并把这个公共类型当做数组的元素类型。这其实就是类型推断的功能之一。
那么,除了让 Julia 自行推断,我们是否能够自己设定元素值的类型呢?答案是肯定的。示例如下:
julia> Int8[1, 2, 3, 4, 5]
5-element Array{Int8,1}:
1
2
3
4
5
julia>
只要我们把元素类型的字面量放在左中括号的左侧就可以达到目的了。这不仅可以用在数组值的一般表示法上,还可以在拼接数组的时候加以运用。例如:
julia> Int8[[1]; [2,3]; 4; 5]
5-element Array{Int8,1}:
1
2
3
4
5
julia>
理所应当,只要我们提供的元素值中有一个不能被转换成目的类型的值,Julia 就会立即报错:
julia> Int8[[1]; [2,3]; 4; "5"]
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int8
# 省略了一些回显的内容。
julia>
到目前为止,我们一直说的都是一维数组的表示法。下面我们来讲怎样表示二维数组。表示二维数组的标准方式是,在方括号中嵌入多个长度相同的一维数组,并用空格分隔这些数组,如:
julia> [[1,2] [3,4] [5,6]]
2×3 Array{Int64,2}:
1 3 5
2 4 6
julia>
回显内容中的2×3
是指,这个二维数组包含了 2 行 3 列。这从下面的数组元素值展示上也能看得出来。不过,如果行数或列数太多的话,数组的元素值也不会全都被展示在这里。因此,我们往往还是要以上面的那个IxJ
样式的信息为准。
依据回显的内容,我们还可以知道,嵌入在这个二维数组中的每一个一维数组都独立地表示了一列值。它们代表的依然是列向量。实际上,把多个长度相同的列向量横向地拼接在一起就形成了矩阵。具体到上述示例,在形状上把[1,2]
、[3,4]
和[5,6]
都顺时针旋转 90 度(即还原为列向量的原本形状),然后再把它们横向地拼接在一起,就形成了我们要表示的 2 行 3 列的矩阵。
你可能已经有所猜测,列向量之间的那些空格好像起到了拼接的作用。没错,在数组值字面量的上下文中,这些空格与英文分号一样也是用于拼接的符号。但不同的是,英文分号用于纵向的拼接,而空格用于横向的拼接。一旦明确了它们的作用,我们就可以探索出它们的更多用法。
比如,我们可以把上述列向量中的分隔符(即英文逗号)替换成空格,就像下面这样:
julia> [1 2]
1×2 Array{Int64,2}:
1 2
julia>
如此一来,这个数组值的字面量就可以表示一个 1 行 2 列的矩阵了。你可能会有疑问,这个字面量表示的数组为什么是二维的,而不是一维的?其原因是,Julia 只认可列向量,而不认可所谓的行向量。
从线性代数的角度讲,列向量和行向量都可以被看做是特殊形状的矩阵。进一步说,列向量是Ix1
的矩阵(即只有一列的矩阵),而行向量是1xJ
的矩阵(即只有一行的矩阵)。但是,在 Julia 中只有列向量可以独立的存在,并由一维数组表示。而行向量却只能作为特殊形状的矩阵,并且没有独立的表示法。
因此,我们在上面编写的数组值字面量[1 2]
会被 Julia 翻译成[[1] [2]]
。也就是说,在它表示的矩阵中,1
是第一个列向量中唯一的元素值,而2
则是第二个列向量中唯一的元素值。那个 1 行 2 列的矩阵就是这样形成的。我们再来看一个例子:
julia> [[1] [2 3] 4 5]
1×5 Array{Int64,2}:
1 2 3 4 5
julia>
这个字面量中的空格作为拼接符号会把那两个嵌入的数组(即[1]
和[2 3]
)都拆解掉,并将其中的元素值与后面的那两个独立的值(即4
和5
)一起作为新数组的元素值。因此,这个字面量表示的就是一个 1 行 5 列的二维数组,当然也可以说它表示的是一个特殊形状的矩阵。
现在,我们同时使用两个拼接符号来表达二维数组,代码如下:
julia> [[1;2] [3;4] [5;6]]
2×3 Array{Int64,2}:
1 3 5
2 4 6
julia> [[1 2]; [3 4]; [5 6]]
3×2 Array{Int64,2}:
1 2
3 4
5 6
julia>
在第一个字面量里,我用英文分号分隔嵌入数组中的多个元素值,并用空格分隔多个嵌入数组。或者说,我把空格用在了外层,把英文分号用在了内层。如此一来,每一个嵌入数组就都表示一个列向量。我们再把这些列向量横向地拼接在一起就形成了 2 行 3 列的矩阵。
而在第二个字面量里,我使用英文分号和空格的方式正好相反,即:空格在内层,英文分号在外层。这样的话,每一个嵌入数组就都表示一个只有一行的矩阵(或者说行向量)。我们再把这些只有一行的矩阵纵向地拼接在一起就形成了 3 行 2 列的矩阵。
可以看到,只要我们层次分明地使用英文分号和空格,就可以灵活地利用它们来表示各种 I 行 J 列的矩阵。不过,对于拥有更多维度的数组,这种表示法就无能为力了。例如,即使我们像下面这样编写字面量,也仍然无法表示一个三维数组:
julia> [ [[1.0;2.0] [1.1;2.1]]; [[3.0;4.0] [3.1;4.1]]; [[5.0;6.0] [5.1;6.1]] ]
6×2 Array{Float64,2}:
1.0 1.1
2.0 2.1
3.0 3.1
4.0 4.1
5.0 5.1
6.0 6.1
julia>
但幸运的是,Julia 提供了不少函数,可以被用来构造多维度的数组值。我们马上就会讲到它们。