2.6 数组和矩阵
数组是对象的可索引集合,例如整数、浮点数和布尔值,它们被存储在多维网格中。Julia中的数组可以包含任意类型的值。在Julia中本身就存在数组这个概念。
在大多数编程语言中,数组的下标都是从0开始的。但是在Julia中,数组的下标是从1开始的。
【范例2-26】数组测试
如下代码简单测试了数组的定义和访问。
01 julia> simple_array = [100,200,300,400,500] #创建一个数组 02 5-element Array{Int64,1}: 03 100 04 200 05 300 06 400 07 500 08 julia> simple_array[2] #访问数组 09 200 10 julia> simple_array[2:4] 11 3-element Array{Int64,1}: 12 200 13 300 14 400
我们可以通过代码01行的形式创建一个数组。代码01行创建了一个名为simple_array的数组,该数组包含5个数。创建完成之后,REPL会立即在屏幕上打印出这个数组的内部数据。我们使用下标来访问数组中的数据,这和其他编程语言类似,唯一不同的就是Julia的数组下标是从1开始的。代码08行访问了数组中的第二个数据200,可以输入simple_array[2],结果为200。同样,可以输出数组中的一段数据,例如代码10行使用“:”来说明输出哪些数据,simple_array[2:4]表示获取数组下标2、3和4的值。
【范例2-27】随机创建数组
如下代码展示了如何通过函数随机创建一个数组。
01 julia> rand_array = rand(1:1000,6) #创建一个数组,使用随机生成 02 的值 03 6-element Array{Int64,1}: 04 813 05 117 06 261 07 478 08 319 09 787
代码01行使用rand函数创建了一个数组,该函数接收两个值,其中第一个值是范围,用“:”表示;第二个值是一个数。本例创建了一个具有6个元素的数组。
前面我们讨论的数组元素的类型是相同的。
【范例2-28】创建具有不同类型元素的数组
如下代码创建了一个具有不同类型元素的数组,但是一些元素会自动提升它的类型。
01 #数组中的元素类型是不同的 02 julia> another_simple_array = [100,250.20,500,672.42] 03 4-element Array{Float64,1}: 04 100.0 05 250.2 06 500.0 07 672.42
在这段代码中,我们使用Float和Int数据来创建一个数组。在Julia中创建数组时会将Int类型转换为Float类型。一般来说,Julia会尝试使用promote()函数来提升类型。如果不能提升,数组将会变成Any类型。
【范例2-29】Any类型数组
如下代码创建了一个Any类型数组。
01 julia> [1,2,"abc"] 02 3-element Array{Any,1}: 03 1 04 2 05 "abc"
代码01行在数组中输入了Int和字符串类型的元素,我们知道这两个元素是不能提升类型的,所以该数组为Any类型。
2.6.1 Julia中的列表解析式
通过列表推导创建数组更加容易,接下来我们就创建一个数组,并用2的幂来填充数组。
【范例2-30】使用列表解析式创建数组
01 julia> pow2 = Array(Int64,10) #创建一个大小为10的数组 02 10-element Array{Int64,1}: 03 … 04 #分配2为第一个元素 05 julia> pow2[1] = 2 06 2 07 #使用列表解析式,以2的幂来填充数组 08 julia> [pow2[i] = 2^i for i = 2:length(pow2)]; pow2 09 10-element Array{Int64,1}: 10 2 11 4 12 8 13 16 14 32 15 64 16 128 17 256 18 512 19 1024
使用列表解析式很方便,代码01行首先创建了一个大小为10的数组,然后代码05行将该数组的第一个元素赋值为2,最后代码08行使用列表解析式来填充剩余的空间。
尽管数组的大小是固定的,但Julia提供了可用于改变数组大小的功能——我们可以创建一个空数组并添加元素。
【范例2-31】创建一个空数组并添加元素
01 julia> empty_array = Float64[] #创建一个空数组 02 0-element Array{Float64,1} 03 julia> push!(empty_array,1.0) 04 1-element 05 Array{Float64,1}: 06 1.0 07 julia> push!(empty_array,1.1,1.2) 08 3-element Array{Float64,1}: 09 1.0 10 1.1 11 1.2
在这段代码中,我们创建了一个空数组empty_array,然后使用push!()函数向其中添加元素。该函数接收一个数组名和一系列值,如代码07行所示,这些值通过“,”分隔。
我们可以使用append!()函数将一个数组中的所有元素添加到另一个数组中。
【范例2-32】添加数组元素
01 julia> append!(empty_array,[101.1,202.2,303.3]) 02 6-element Array{Float64,1}: 03 1.0 04 1.1 05 1.2 06 101.1 07 202.2 08 303.3
代码01行传入一个数组,该数组内的所有元素将被添加到empty_array数组中。
我们还可以创建自定义大小的数组,代码如下。
【范例2-33】创建自定义大小的数组
01 julia> X = Array{Int64}(4,1) 02 4×1 Array{Int64,2}: 03 140044411365992 04 140044411366424 05 140044411368648 06 140044414849552
以上代码简单演示了如何创建一个自定义大小的数组。
我们也可以使用fill!()函数向数组中填充相同的值。
【范例2-34】数组的填充
01 #使用fill!()函数复制值 02 julia> fill!(X,4) 03 4×1 Array{Int64,2}: 04 4 05 4 06 4 07 4 08 julia> X[2] = 10; X #使用下标改变值 09 4×1 Array{Int64,2}: 10 4 11 10 12 4 13 4
代码02行将之前创建的数组X填充为4,代码08行将数组中第二个元素的值改变为10。
对整数有效的各种运算符和函数对数组也有效,以下是常用的运算符。
• 一元运算符:−, +, !。
• 二元运算符:+, −, * , .* , /, ./, \, .\, ^, .^, div, mod。
• 比较运算符:.==, .!=, .<, .<=, .>, .>=。
• 一元布尔或位运算符:~。
• 二元布尔或位运算符:&, |, $。
2.6.2 矩阵运算
本节我们将创建矩阵,并展示它的一些功能和特性。
【范例2-35】矩阵构造
以下代码构造了一个Julia矩阵。
01 julia> X = [1, 1, 2, 3, 5, 8, 13, 21, 34] #创建一个数组 02 9-element Array{Int64,1}: 03 1 04 1 05 … 06 julia> X = [1 1 2; 3 5 8; 13 21 34] #构造矩阵 07 3×3 Array{Int64,2}: 08 1 1 2 09 3 5 8 10 13 21 34
代码01行创建了一个含有9个元素的数组X,之后又创建了一个3×3的矩阵X,从代码06行可以看到矩阵的构造方法。矩阵使用“;”来分隔行,使用空格来分隔每个元素。
我们可以对矩阵进行一些操作。
01 julia> A = [2 4; 8 16; 32 64] #3×2的矩阵 02 3×2 Array{Int64,2}: 03 2 4 04 8 16 05 32 64
代码01行定义了一个3×2的矩阵A。
我们可以使用reshape()函数来改变矩阵A的形状。
01 julia> reshape(A,2,3) 02 2×3 Array{Int64,2}: 03 2 32 16 04 8 4 64
也可以使用转置函数来实现这个操作。
01 julia> transpose(A) 02 2×3 Array{Int64,2}: 03 2 32 16 04 8 4 64
代码01行使用transpose()函数来实现矩阵转置。
我们也可以对矩阵进行加法和乘法运算。矩阵加法运算代码如下:
01 julia> B = [1 1 2; 3 5 8] 02 2×3 Array{Int64,2}: 03 1 1 2 04 3 5 8 05 #矩阵相加 06 julia> transpose(A)+B 07 2×3 Array{Int64,2}: 08 3 9 34 09 7 21 72
矩阵相加操作执行成功。现在,我们来看一看矩阵的乘法运算。
01 julia> transpose(A)*B 02 ERROR: DimensionMismatch("matrix A has dimensions (2,3), 03 matrix B has dimensions (2,3)") 04 …
我们可以预料到这个操作将会报错,因为矩阵B应该是3×2的。下面转置矩阵B并再次执行操作。
01 julia> transpose(A)*transpose(B) #置换矩阵B的乘法 02 2×2 Array{Int64,2}: 03 74 302 04 148 604
Julia还提供了一个特殊运算符——就是在普通的运算符前面添加“.”,它可以帮助我们进行元素乘法运算。
01 julia> A.*B #下面对两个(2×3)矩阵执行元素运算 02 2×3 Array{Int64,2}: 03 2 8 64 04 12 80 512
这里使用A.*B对两个相同维度的矩阵进行运算。除此之外,矩阵同样支持如下元素运算。
01 julia> A = [1 2 2 ; 4 5 6] #定义二维矩阵(2×3) 02 2×3 Array{Int64,2}: 03 1 2 2 04 4 5 6 05 julia> B = [0 2 3 ; 4 4 6] 06 2×3 Array{Int64,2}: 07 0 2 3 08 4 4 6 09 julia> A .== B 10 2×3 BitArray{2}: 11 false true false 12 true false true
代码01行和05行分别定义了两个矩阵A和B,然后使用“.==”来比较其相对应的元素,返回一个布尔值的矩阵。本例的结果如代码11行和12行所示。
2.6.3 多维数组操作
上一节我们介绍了Julia的数组、矩阵及其运算。本节我们将介绍Julia中的多维数组。首先使用rand()函数构造一个3×3×3的多维数组,该函数生成随机值。
【范例2-36】多维数组
以下代码展示了多维数组的一些用法。
01 julia> multiA = rand(3,3,3) 02 3×3×3 Array{Float64,3}: 03 [:, :, 1] = 04 0.461755 0.594271 0.410017 05 0.168575 0.821246 0.779109 06 0.993498 0.516948 0.59578 07 [:, :, 2] = 08 0.256123 0.19102 0.503787 09 0.628641 0.00406246 0.309143 10 0.939811 0.00935249 0.926403 11 [:, :, 3] = 12 0.263566 0.574306 0.203657 13 0.447232 0.509143 0.123887 14 0.760373 0.790011 0.29599
我们可以使用下标,就像访问数组和矩阵中的值一样。
01 julia> multiA[1,3,2] 02 0.5037874039027417
上面的数字在数组中实际上表示为0.503787。我们还可以使用reshape()函数将数组重新“整形”为二维数组。
01 julia> reshape(multiA,9,3) 02 9×3 Array{Float64,2}: 03 0.461755 0.256123 0.263566 04 0.168575 0.628641 0.447232 05 0.993498 0.939811 0.760373 06 0.594271 0.19102 0.574306 07 0.821246 0.00406246 0.509143 08 0.516948 0.00935249 0.790011 09 0.410017 0.503787 0.203657 10 0.779109 0.309143 0.123887 11 0.59578 0.926403 0.29599
2.6.4 稀疏矩阵
前面我们介绍了矩阵和多维数组。在范例中,矩阵和数组中的每一个元素都是用具体的值来填充的。但在很多情况下,可能会有很多0值。如果0值比具有值的数据量多,则建议将它们存储在高效且专门设计的稀疏矩阵数据结构中。
【范例2-37】创建稀疏矩阵
如下代码创建了一个稀疏矩阵。
01 julia> sm = spzeros(5,5) 02 5×5 SparseMatrixCSC{Float64,Int64} with 0 stored entries 03 julia> sm[1,1] = 10 04 10 05 julia> sm 06 5×5 SparseMatrixCSC{Float64,Int64} with 1 stored entry: 07 [1, 1] = 10.0