一维数组

数组

在计算机中,数组通常用于存储一系列有序的数据。它们提供了一个可以用一个或几个数字索引的存储空间。在科学计算领域,数组被用于表示向量。

特别地,有一些数组支持可变的长度,这包括 Julia 的默认数组类型。你也可以选择使用 StaticArrays 包获得固定长度的数组。

Julia 内置的一维数组类型名是 Vector,它是 Julia 提供的任意维数组 Array 的一个特例。

初始化

可以使用字面量或各种函数初始化一个一维数组。

julia> [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> zeros(Int, 3)
3-element Vector{Int64}:
 0
 0
 0

julia> ones(Int, 3)
3-element Vector{Int64}:
 1
 1
 1

julia> collect(1:4)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> collect("a" => 1)
2-element Vector{Union{Int64, String}}:
  "a"
 1

julia> Vector{Int}(undef, 3) # 相当于 C 中未初始化的数组
3-element Vector{Int64}:
         1
 470028848
         1

索引/切片访问

数组最基本的功能是基于索引(index)进行访问,获取或修改对应位置的值。

由于一维数列 a 的第 i 个元素在数学上记作 a_i,索引也被称为「下标」。

在 C 系语言中,数组索引从 0 开始,同时区间是左闭右开的,它的好处是可以节省部分计算时间,同时 0 有时可以作为缺省值使用。

而在 Julia 中,数组索引从 1 开始,同时区间通常是闭区间,它的好处是阅读更直观。

你也可以选择使用 OffsetArrays 包获得可自定义索引起点的数组类型。

julia> a = collect(1:10);

julia> a[2]
2

julia> a[2:5]
4-element Vector{Int64}:
 2
 3
 4
 5

julia> length(a) # 查看长度
10

julia> a[end] # 局部使用 end 表示 length(a)
10

julia> a[2] = 20
20

julia> a[1:2] = [10, 20]
2-element Vector{Int64}:
 10
 20

julia> a[:] # 表示范围整个范围,可以用于多维数组
10-element Vector{Int64}:
...

相关常用函数

Tips

可以使用 methodswith(Vector) 获取 Vector 特有的相关函数列表。 若希望查看所有可应用于 Vector 的函数,可使用 methodswith(Vector;supertypes=true)

以下表格中,参数列表第一个均为 v::Vector{T},将被省略。“弹出”是指元素从数组中被移除,这会影响之后的元素的索引。

函数名参数列表描述
empty!清空
isempty是否为空
first获得第一个元素
popfirst!获得并弹出第一个元素
pushfirst!items...在 v 最前推入 items,推入后顺序与 items 排列顺序一致
last获得最后一个元素
pop!获得并弹出最后一个元素
push!items...在 v 最后推入 items,推入后顺序与 items 排列顺序一致
popat!i::Integer, [default]弹出 v 中第 i 个元素;若不存在则返回 default(若有),抛出错误(若无)
deleteat!i::Integer 或 长度与 v 相同的 Bool 数组 或 可以表明若干个索引的东西删除指定的一个或多个数据
insert!i::Integer, itemv[i] 前插入 item
append!collections::AbstractVector{T}...在末尾添加数组中的数据,顺序与传参顺序一致(对于添加多个数组的支持需要Julia 1.6)
prepend!collections::AbstractVector{T}...在开头添加数组中的数据,顺序与传参顺序一致(对于添加多个数组的支持需要Julia 1.6)
resize!n::Integer将 v 的长度修改为 n,多的部分截断,少的部分用 undef 补齐
sizehint!n::Integer建议 v 修改容纳范围至 n 以便之后使用
splice!index, [replacement]在给定位置移除并插入

边界检查

使用 [] 或类似方法访问时,Julia 会默认进行边界检查,若越界会抛出 BoundsError。 如果你十分确信需求范围在边界内,可以在前面加上 @inbounds 宏修饰。此时越界可能会得到错误结果或奔溃等。

遍历

我们常常希望对数组中每个元素作操作而不是分别作操作。

最一般的做法是使用循环:

julia> a = [2, 3, 5, 7]
4-element Vector{Int64}:
 2
 3
 5
 7

julia> for i in 1:4 # 更好的选择是 enumerate(a)
           a[i] = a[i] ^ 2
       end

julia> a
4-element Vector{Int64}:
  4
  9
 25
 49

这可以被 foreachmap 简化

julia> a = [2, 3, 5, 7]
4-element Vector{Int64}:
 2
 3
 5
 7

julia> foreach(x -> print(x), a)
2357
julia> map(+, a, a)
4-element Vector{Int64}:
  4
  6
 10
 14
julia> map!(x -> x^2, a, a)
4-element Vector{Int64}:
  4
  9
 25
 49

向量点运算

Julia 中,每个二元运算符都有一个 “点” 运算符与之对应,例如 ^ 就有对应的 .^ 存在。这个对应的 .^ 被 Julia 自动地定义为逐元素地执行 ^ 运算。比如 [1,2,3] ^ 3 是非法的,因为数学上没有定义数组的立方。但 [1,2,3] .^ 3 在 Julia 里是合法的,它会逐元素地执行 ^ 运算(或称向量化运算),得到 [1^3, 2^3, 3^3]。类似地,! 这样的一元运算符,也都有一个对应的 .√ 用于执行逐元素运算。

julia> [1,2,3] .^ 3
3-element Vector{Int64}:
  1
  8
 27

julia> @. sqrt([1,2,3])
3-element Vector{Float64}:
 1.0
 1.4142135623730951
 1.7320508075688772

更确切地说,a .^b 被解析为 “点运算” 调用 (^).(a,b),这会执行广播操作。

除了点运算符,我们还有逐点赋值运算符,类似 a .+= b

将点运算符用于数值字面量可能会导致歧义,如 1.+x,因此遇到这种情况时,必须明确地用空格消除歧义。[1]

参阅

练习

  • (困难)了解什么是动态规划,并使用一维数组完成一些简单的动态规划问题。

  • 1

    https://docs.juliacn.com/latest/manual/mathematical-operations/#man-dot-operators