Julia 数组的构造

                     

贡献者: 待更新

   本文授权转载自郝林的 《Julia 编程基础》。原文链接:第 9 章 容器:数组(上)

9.3 数组的构造

   关于可以构造数组值的那些函数,首当其冲的肯定是 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:4×3×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。但这只是初始化元素值的一种选项而已。我们还可以选择 nothingmissing 作为这个参数的值。但前提是,该数组的元素类型必须是 nothingmissing 的类型的超类型。

   nothingmissing 也都是常量,其含义同样比较特殊。我们在前面的章节中对它们都做过解释。nothing 代表着单例类型 Nothing 的唯一值,它的含义是 “此处没有值”。而 missing 则代表单例类型 Missing 的唯一值,它的含义是 “此处的值是缺失的”。

   那怎样设定数组的元素类型才能让它成为 NothingMissing 的超类型呢?这个时候,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 类型的零值是 0x00Complex 类型的零值是 0+0imRational 类型的零值是 0//1,等等。

   与之类似,ones 函数可以创建元素值全为 1 的数组。其参数的定义与 zeros 函数的参数定义相同。仍要注意,不同的数值类型表示 1 的方式也不同。

   还有一个名叫 fill 的函数,它有两个参数:xdims。参数 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>

   另外,函数 truesfalses 也很常用。它们都只有一个名为 dims 的参数。trues 函数用于创建元素值全为 true 的数组,而 falses 函数则用于创建元素值全为 false 的数组。注意,它们创建的数组的类型并不是 Array,而是 BitArray

   BitArray 类型也被称为位数组类型。它是元素类型为 BoolArray 类型的优化版本。它仅使用 1`Bool`类型的每一个值都需要占用 8 个比特。这就意味着,位数组在存储空间的利用率方面有着 8 倍的提升。为了与标准的存储方式保持兼容,从位数组取出的元素值会被还原成(新的)常规的布尔值。

   以上就是我们构造数组值的时候经常会用到的函数。当然,还有一些函数也可以被用来构造数组值,如函数 randrandncollectsimilarreinterpret 等。不过,这些函数在功能上就没有那么的纯粹了。

                     

© 小时科技 保留一切权利