贡献者: 待更新
本文授权转载自郝林的 《Julia 编程基础》。原文链接:第 9 章 容器:数组(上)。
关于可以构造数组值的那些函数,首当其冲的肯定是 Array
类型附带的构造函数。
我们先说 Array{T}(undef, dims)
和 Array{T,N}(undef, dims)
。这两个函数都是用来构造未初始化的 N 维数组的。其中的 T
依然代表元素类型,N
依然代表维数。
undef
是一个常量,它代表着单例类型 UndefInitializer
的唯一值。所谓的单例类型,是指有且仅有一个实例的类型。无论我们实例化这种类型多少次,都只会得到同一个值,即该类型的唯一值。UndefInitializer
类型专用于数组的初始化,其值表达的含义是创建一个未初始化的数组。或者说它表达的是,上述构造函数的调用者不想向这个数组填充任何的元素值。这时,Julia 会在该数组的所有元素位置上填充随机值。
我们在前面讲了,数组类型的字面量上不会体现出数组在各个维度上的元素数量。然而,这些数量却是构造一个多维数组时必须要确定的信息。注意,对于多维数组,我们所说的在某个维度上的元素指的是可能一个个元素值,也可能是一个个低维数组。这在后面会有更详细的解释。
这里的参数 dims
的作用正是表示数组在各个维度上的元素数量。更确切地说,它表示的是各个维度的长度。dims
是 dimensions 的缩写。它的值可以是一个包含了若干个整数的元组值,也可以是若干个由英文逗号分隔的整数值。不过后者只在三维及以下的数组构造中才有效。下面是一个例子:
julia> Array{Int64}(undef, 4, 3, 2)
4×3×2 Array{Int64,3}:
[:, :, 1] =
4683772848 4667574256 4667574256
4490317616 4667575152 4667574256
4490317616 4667574256 4667575152
4667574256 4490317616 4667574256
[:, :, 2] =
4490317616 4667572800 0
4490317472 0 0
4667574256 0 0
4488855536 0 4680843264
julia>
请注意,回显内容中表示的是一个 4×3×2
的三维数组。还记得吗?我们可以把三维数组比喻成一座停车楼。那么上面这个三维数组就相当于一个有 2 层的停车楼。现在,你要带着这个想象跟我一起理解它的展示格式。
回显内容的第一行反映了我们构造数组时给予的信息。第二行中的 [:, :, 1]
指的是在第三个维度上的第 1 个低维数组(即二维数组),相当于停车楼的上一层。你也可以把 [:, :, 1]
看成一个特殊的数组,其中的每一个元素的值都用于代表上述三维数组在对应维度上的某个低维数组。这个特殊的数组中的前两个元素都由英文冒号 :
占位,相当于选择了对应维度上的所有低维数组。而其中的最后一个元素值是 1
,代表的正是上述三维数组中的第 1 个二维数组。由此,在它下面才展示了对应的二维数组中的所有元素值,相当于俯瞰停车楼的上一层。
我们已经知道,只要 N 大于 1,那么 N 维数组就都可以被看做是由一个个尺寸相同的 N-1 维的数组拼接而成的结构,就像停车楼的每一层都是一个停车场那样。因此,在上述数组的第三个维度上的第 1 个低维数组就应该是一个 4×3
的二维数组。在 [:, :, 1]
下面的那 4 行内容展示的正是这个二维数组。其中的所有元素值都是由 Julia 自行填充的随机值。
又由于上述三维数组在第三个维度上的长度是 2,所以才有了再下面的 [:, :, 2]
,以及与它对应的又一个 4×3
的二维数组,相当于停车楼的下一层。
让我们再来构造一个四维数组:
julia> Array{Int64, 4}(undef, (4, 3, 2, 2))
4×3×2×2 Array{Int64,4}:
[:, :, 1, 1] =
4688801328 4688801456 4688801680
4688801360 4688801488 4688801712
4688801392 4688801616 4688801744
4688801424 4688801648 4688801776
[:, :, 2, 1] =
4688801808 4620636144 4688805040
4688801840 4688935952 4688805072
4688854576 4688991056 4688805104
4688935312 4688991088 4688986896
[:, :, 1, 2] =
4688805264 4620632072 4688805456
4688987472 4688988016 4679072384
4688805328 4688988176 4679072480
4679071744 4688805424 4688989008
[:, :, 2, 2] =
4688989104 4679073120 4679073520
4688989200 4679073216 4679073680
4688805584 4679073312 4679073728
4688989712 4688990032 4688796304
julia>
四维数组可能会挑战到你的空间想象力。但有了前面的解释,这个四维数组的展示格式就应该容易理解一些了。这个四维数组由 2 个 4×3×2
的三维数组拼接而成,而这 2 个三维数组又分别由 2 个 4×3
的二维数组拼接而成。所以,[:, :, 1, 1]
指的就是,这个四维数组中的第 1 个三维数组中的第 1 个二维数组。而 [:, :, 2, 1]
指的则是,这个四维数组中的第 1 个三维数组中的第 2 个二维数组。以此类推。紧挨在它们下面的那几行内容展示的就是对应的二维数组。你明白了吗?你可以再花一些时间思考一下。
为什么 Julia 会这样展示多维数组呢?这主要是因为,我们在平面(如屏幕、纸张等)之上最多只能铺开二维的数组。虽然我们也可以在纸上画出三维的物体(如六面体、球体等),但那终归只是一种视觉上的效果。而且,那些物体只能被当作图形来看待,很难完全用普通的文本直观地展示出来。即使我们生活在三维的世界里,可所用的文字和语言都只是二维的。这也是我们不容易理解四维以及更多维数的原因。总之,Julia 在用二维的方式展示多维数组。它把多维数组拆分成了一个个二维数组,并以普通文本的形式摆在我们面前。
言归正传。上例调用的是 Array{T,N}(undef, dims)
。这时我们需要注意,替代 N
的那个整数值一定要等同于替换掉 dims
的那个元组值的长度(或者替换掉 dims
的那些整数值的数量),否则 Julia 就会立即报错。因为两边给定的数组维数不一致。
在前面,我们传给数组构造函数的第一个参数值一直是 undef
。但这只是初始化元素值的一种选项而已。我们还可以选择 nothing
或 missing
作为这个参数的值。但前提是,该数组的元素类型必须是 nothing
或 missing
的类型的超类型。
nothing
和 missing
也都是常量,其含义同样比较特殊。我们在前面的章节中对它们都做过解释。nothing
代表着单例类型 Nothing
的唯一值,它的含义是 “此处没有值”。而 missing
则代表单例类型 Missing
的唯一值,它的含义是 “此处的值是缺失的”。
那怎样设定数组的元素类型才能让它成为 Nothing
或 Missing
的超类型呢?这个时候,Union
类型就派上用场了。不要忘了,它的字面量可以表达多个类型的联合。因此,我们把元素类型设定为 Union{Nothing, String}
就意味着该数组的元素值既可以是一个字符串值,也可以是 nothing
。对于 Missing
来说也是类似的。下面是一些使用示例:
julia> Array{Union{Nothing, String}}(nothing, 2, 2)
2×2 Array{Union{Nothing, String},2}:
nothing nothing
nothing nothing
julia> Array{Union{Missing, Int64}}(missing, 2, 3)
2×3 Array{Union{Missing, Int64},2}:
missing missing missing
missing missing missing
julia>
可以看到,如果我们传给数组构造函数的第一个参数值是 nothing
,那么此次被创建出的数组的所有元素值就都会是 nothing
。若传入 missing
的话也是类似的。
除了上面讲的构造函数,Julia 还提供了另外的一些可以创建多维数组的函数。比如,函数 zeros
可以创建元素值全为零值的数组。示例如下:
julia> zeros(Int32, 4, 3)
4×3 Array{Int32,2}:
0 0 0
0 0 0
0 0 0
0 0 0
julia> zeros(Float32, 4, 3)
4×3 Array{Float32,2}:
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
julia>
zeros
函数的第一个参数的名称是 T
,代表元素类型。这个参数是可选的,如果我们选择不为它传入值,那么其值就是缺省的 Float64
。该函数的第二个参数的名称是 dims
,与前述的构造函数中的 dims
含义相同。
注意,这个函数的第一个参数值通常只能是一个数值类型。更具体地说,它可以是任意的布尔类型、整数类型、浮点数类型、复数类型,以及有理数类型。另外,对于不同的数值类型,其零值也是不同的。所谓的零值,就是用来表示 0
的值。比如,UInt8
类型的零值是 0x00
、Complex
类型的零值是 0+0im
,Rational
类型的零值是 0//1
,等等。
与之类似,ones
函数可以创建元素值全为 1
的数组。其参数的定义与 zeros
函数的参数定义相同。仍要注意,不同的数值类型表示 1
的方式也不同。
还有一个名叫 fill
的函数,它有两个参数:x
和 dims
。参数 x
代表的值将会被填充到新数组的所有元素位置上。显然,新数组的元素类型由 x
决定。与前面一样,新数组的维数和大小仍由 dims
决定。下面是一个示例:
julia> fill(1.0f-3, 2, 3)
2×3 Array{Float32,2}:
0.001 0.001 0.001
0.001 0.001 0.001
julia>
另外,函数 trues
和 falses
也很常用。它们都只有一个名为 dims
的参数。trues
函数用于创建元素值全为 true
的数组,而 falses
函数则用于创建元素值全为 false
的数组。注意,它们创建的数组的类型并不是 Array
,而是 BitArray
。
BitArray
类型也被称为位数组类型。它是元素类型为 Bool
的 Array
类型的优化版本。它仅使用 1`Bool`类型的每一个值都需要占用 8 个比特。这就意味着,位数组在存储空间的利用率方面有着 8 倍的提升。为了与标准的存储方式保持兼容,从位数组取出的元素值会被还原成(新的)常规的布尔值。
以上就是我们构造数组值的时候经常会用到的函数。当然,还有一些函数也可以被用来构造数组值,如函数 rand
、randn
、collect
、similar
、reinterpret
等。不过,这些函数在功能上就没有那么的纯粹了。