Engee 中的数组、向量和矩阵
导言
在*Engee*中,数组、向量和矩阵用于处理数据、参数和复杂结构。这些结构由Julia 语言实现,可以高效地存储和处理数值信息,包括计算结果、信号和模型参数。本文将讨论它们各自的特点。
在 Julia 中,数组、矢量和矩阵的元素编号以 "1 "开头,而不是像其他语言那样以 "0 "开头。最后一个元素的索引为 "end"。这与数学的传统是一致的,数学的索引也是从 1 开始。例如,数组 a = [1, 2, 3] 的第一个元素用 a[1] 访问,最后一个元素用 `a[end]`访问。详见下文。
|
数组*是一种通用的多维容器,可以包含任何类型的元素。Julia 中的数组支持任意维数,是存储和处理数据的多功能工具:
A = rand(2, 2, 3)
输出
2×2×3 Array{Float64, 3}:
[:, :, 1] =
0.159525 0.787948
0.358068 0.124023
[:, :, 2] =
0.228296 0.292929
0.712896 0.0253828
[:, :, 3] =
0.3547 0.837655
0.474441 0.201402
数组有动态和静态之分:
-
*动态数组*是在程序执行过程中其大小可能发生变化的数组。这些数组存储在内存中,可能会进行扩展,这使得它们更加灵活,但对于计算密集型任务来说,却不是最佳选择。
例如
dynamic_array = [1, 2, 3] dynamic_array[2] = 5 # 赋值 push!(dynamic_array, 4) # 添加元素 deleteat!(dynamic_array, 3) # 删除索引为3的项
输出
4-element Vector{Int64}: 1 5 3 4
-
*静态数组*是具有固定大小的数组,大小在创建阶段设定。它们以连续数据块的形式存储在内存中,这样可以最大限度地减少内存管理开销,从而更快地执行操作。
当静态数组的大小小于 100-200 个元素时,其效率较高。如果大小大于 100-200 个元素,则通常使用动态数组。 在 Julia 中,静态数组由 StaticArrays.jl 包提供。因此,要使用静态数组,需要使用
using StaticArrays
命令连接 该软件包。示例
using StaticArrays static_array = @SVector [1, 2, 3]
输出
3-element SVector{3, Int64} with indices SOneTo(3): 1 2 3
代码中使用了一个宏脚注:
@SVector
,用于创建一个固定大小的静态一维数组(向量)。静态数组还有其他有用的宏:静态数组宏_。
-
@SVector
- 创建一个固定大小的不可变静态向量。例如`@SVector [1, 2, 3]`。 -
@MVector
- 创建一个固定大小的可变静态向量。示例`@MVector [1, 2, 3]`. -
@SMatrix
- 创建一个固定大小的可变静态矩阵。示例`@SMatrix [1 2; 3 4]`. -
@MMatrix
- 创建一个固定大小的可变静态矩阵。示例`@MMatrix [1 2; 3 4]`. -
@SizedArray
- 将普通数组转换为固定大小的静态数组。示例`@SisedArray rand(2, 2)`. -
@SArray
- 创建一个固定大小的普通静态数组,可以是向量、矩阵或高维数组。示例`@SArray rand(2, 2, 2, 2)`. -
@MArray
- 创建一个通用的可变静态数组。示例`@MArray rand(2, 2, 2, 2)`. -
@StaticArray
- 一个通用宏,用于创建任意形状的不可变数组。示例`@StaticArray rand(2, 2, 2)`. -
@MutableStaticArray
是创建可变静态数组的通用宏。示例`@MutableStaticArray rand(2, 2)`. -
@zeros
- 创建一个充满 0 的静态数组。示例`@zeros(SVector, 3)` 创建一个包含三个零的向量。 -
@ones
- 创建一个充满 1 的静态数组。示例`@ones(SMatrix, 2, 2)` 创建一个 2×2 的 1 矩阵。 -
@fill
- 创建一个充满给定值的静态数组。示例@fill(SVector,3,42)`创建一个由三个元素组成、值为 42 的矢量。 -
@static
- 通过应用静态数组的属性,在编译阶段优化代码块的执行。示例
@static if length(SVector(1, 2, 3)) == 3 println("5分的大小是3分") end
-
与 C++ 不同,Julia 中的数组可以存储不同类型的元素。如果元素类型可以简化为一种通用类型(例如 "有理数 "和 "复数"),Julia 将自动执行类型转换。例如
如果元素类型无法还原为一种通用类型(例如数字和字符串),Julia 会将数组转换为
这使得 Julia 中的数组变得灵活,但值得注意的是,使用 |
矢量*是一个一维数组,是使用索引访问的元素序列。在 Julia 中,矢量可以创建为*矢量列*或*矢量字符串*,但它们的语法和数据类型有所不同:
-
列向量使用逗号或列条目创建:
v = [1, 2, 3] # 列向量
或
v = [1 2 3] # 列向量
输出
3-element Vector{Int64}: 1 2 3
-
Julia 没有单独的矢量字符串类型。取而代之的是,数字字符串被表示为维度为 1 × N 的矩阵:
r = [1 2 3] # 1×3矩阵
输出
1×3 Matrix{Int64}: 1 2 3
在 Julia 中,数组中的分隔符定义了数组的结构:
|
矩阵(Matrix)是一个二维数组,代表一个按行和列排列的数字或其他对象的表格。在 Julia 中,矩阵是以二维数组的形式创建的:
M = [1 2 3; 4 5 6]
输出
2×3 Matrix{Int64}:
1 2 3
4 5 6
在 Julia 中,矢量和矩阵的分离可以有效地组织内存中的数据,这对快速数值计算非常重要。矢量使用一维存储,因此更容易处理数据序列,而矩阵则以二维形式组织,因此更容易执行线性代数(乘法和转置)。这种分离有助于优化缓存内存和 CPU 资源的使用,提高计算效率。
数组、矢量和矩阵之间的区别
维度:
-
数组是具有任意维数的容器。数组有任意维数,例如,
size(A)
可以返回 (2
,2
,3
)。 -
向量是一维数组。向量总是一维的,函数
size(v)
返回元组(n,)
,其中n
是向量的长度。 -
矩阵是一个二维数组。矩阵的维数为二,
size(M)
返回`(n, m)`元组,其中`n`是行数,`m`是列数。
元组是一种不可变的数据结构,可以包含不同类型的多个元素。在 Julia 中,元组用于表示逻辑上相关但无需修改的值。例如,函数`size()`的结果总是以元组的形式返回。 与向量和矩阵不同,元组不是数组,也不是用来执行算术运算的。它有固定数量的元素和不可变的结构,因此在从函数返回多个值或传递参数时非常有用。 |
-
实施特点
-
处理数组的方法和函数适用于向量和矩阵。不过,每种类型的数据都有不同的优化算法。
-
在 Julia 中,矢量和矩阵是数组的特例,其中矢量的维度为 "1",矩阵的维度为 "2"。
-
-
初始化和语法
-
可通过 Array 函数或使用生成器(表达式为"[… for …]")创建任意维数的数组。例如,对于 Array:
A = Array{Float64}(undef, 2, 2, 3)
这里的 undef 是一个关键字,用于创建一个数组而不初始化元素。这意味着将为数组分配内存,但其元素的值将保持未定义(可能是随机的)。
使用生成器的示例
array = [i^2 for i in 1:5] # 数组的每个元素都是一个从1到5的数字的正方形
-
以一维数组的形式创建向量,使用逗号枚举元素:
v = [1, 2, 3]
-
矩阵以分号分隔行 (
;
),行中的元素以空格或制表符分隔:M = [1 2 3; 4 5 6]
-
-
内存存储:
-
具有大量维度的阵列使用更通用的存储方法,这会增加操作的开销。
-
上述差异形成了特定的应用领域:
-
阵列用于模拟更复杂的数据:三维模型、图像阵列或多维时间序列。
-
矢量最常用于一维数据:时间序列、坐标、信号。
-
矩阵表示二维结构:图像、表格、线性方程组。
处理数组、矢量和矩阵的基本功能
Julia 提供了大量处理数组(包括矢量和矩阵)的函数。有关这些函数及其签名的更多信息,请访问数组 。
标有`*`的函数不是标准库的一部分。要使用它们,请安装相应的 Julia 软件包:
导入 "和 "使用 "操作符以及软件包名称可用于访问此类函数的元素。更多信息,请访问使用Julia软件包 。 |
以下是基本函数列表,附有简要说明和示例:
创建
功能名称 |
说明 |
示例 |
|
创建一个元素等于零的数组。 创建对象的类型取决于传递的参数:一维数组(向量)、二维数组(矩阵)或多维数组。 |
这里
|
|
创建元素等于 1 的数组。 创建对象的类型取决于传递的参数:一维数组(向量)、二维数组(矩阵)或多维数组。 |
这里
|
rand`。 |
创建一个数组,其中包含在"[0, 1) "区间内均匀分布的随机数。创建对象的类型取决于传递的参数:一维数组(向量)、二维数组(矩阵)或多维数组。 |
这里
|
填充 |
创建一个填充指定值的数组。创建对象的类型取决于传递的参数:一维数组(向量)、二维数组(矩阵)或多维数组。 |
这里:
|
收集 |
通过转换给定的范围或迭代对象创建数组。创建对象的类型取决于所使用的输入。 |
这里:
|
结构变化
功能名称 |
说明 |
示例 |
push!"。 |
在一维数组(向量)的末尾添加一个元素。仅适用于可修改(动态)数组。 末尾索引也可用于添加元素 (数组索引). |
这里:
|
pop!`。 |
从一维数组(向量)中删除并返回最后一个元素。仅适用于可修改(动态)数组。 |
这里
|
|
在一维数组(向量)的指定位置插入一个元素。仅适用于可修改(动态)数组。 |
这里
|
删除 |
从一维数组(向量)中删除指定索引处的元素。仅适用于可修改(动态)数组。 |
这里
|
使用维数_
功能名称 |
说明 |
示例 |
|
以元组形式返回数组、矩阵或向量的大小,其中每个值都对应特定维度的大小。 |
这里
|
|
改变一维数组(向量)的大小。如果新的大小大于当前的大小,新的元素将以默认值 `0.0`初始化。如果小于当前大小,数组将被修剪为指定大小。 |
这里
|
长度 |
返回数组、向量或矩阵中元素的总数,与它们的维数无关。 |
这里
|
|
返回数组、向量或矩阵的维数。 |
这里
|
|
改变数组、矩阵或向量的维度,将其转换为具有指定行数和列数的新数组。新数组的元素数必须与原始数组的元素数一致。 |
这里:
|
线性代数
函数名 |
说明 |
示例 |
转置 |
返回行列互换的数组或矩阵。对于一维数组(向量),结果取决于其方向。 |
这里
|
* |
计算正方形矩阵的行列式。它是 LinearAlgebra 软件包的一部分(需要安装软件包)。 行列式是一个标量,用于描述矩阵的特性(如可逆性)。 |
此处:
|
|
计算正方形矩阵的逆矩阵。逆矩阵只适用于行列式不为零的矩阵,即可逆矩阵。 即使源矩阵的值是整数("矩阵{Int64}"),"inv "函数也会返回一个 "矩阵{Float64}"类型的逆矩阵。 |
m1 = [2 3; 1 4] inv(m1) m2 = [1 2 3; 0 1 4; 5 6 0] inv(m2) m3 = [1 2; 3 4] inv(m3) 这里:
|
* |
计算矩阵的特征值和向量。例如
|
这里:
|
norm`。 |
计算向量或矩阵的规范。 常模是衡量向量或矩阵 "大小 "的数字特征。默认情况下,计算的是欧氏常模。 |
v = [3, 4] norm(v) m = [1 2; 3 4] norm(m) norm(v, 1) # 3 + 4 = 7.0 norm(m, Inf) #√30 ≈ 5.477 这里:
|
检查
功能名称 |
说明 |
示例 |
|
检查数组、向量或矩阵是否为空。如果对象为空,则返回 |
此处:
|
|
检查对象是否为空。如果对象为空,则返回 支持数值类型以及数组、矩阵和向量(在 Julia 中,如果对象的所有元素都为零,则该对象为空)。 |
这里 用于数字:
对于向量
对于矩阵
对于多维数组:
|
|
检查矩阵是否对称。如果矩阵等于其转置副本,则返回 |
此处:
|
变换.
函数名称 |
说明 |
示例 |
|
将矩阵或多维数组转换为向量。它会返回一个一维数组,其中的元素取自原始矩阵或数组的列。如果参数已经是一个向量,则返回值不变。 |
|
|
重新排列多维数组的测量值(轴)。 可以在不改变数据本身的情况下改变测量的顺序,创建一个具有重新排列的轴的新数组。 |
这里:
|
|
数组的横向和纵向连接(并集)。
|
这里:
|
复制 |
创建数组、向量或矩阵的独立副本。对副本所做的更改不会影响原始对象,反之亦然。 |
这里
|
|
创建数组的深度副本,递归复制所有嵌套结构。对副本的修改不会影响原数组。在 Julia 中,默认赋值传递的是对象引用而不是值。如果需要创建一个独立的副本,有两种方法:
|
这里:
|
广播 |
允许对数组元素进行自动大小匹配(广播)操作。详情请参见此处。 |
此处:
|
使用数组的适当性
数组是一种方便灵活的数据处理工具,但 Julia 的其他结构可能更适合某些任务。详见下文。
何时推荐使用数组
-
通过索引快速访问元素 - 数组在内存中按顺序存储元素,允许通过索引即时访问任何元素。例如,
ar[3]
将立即返回数组的第三个元素。这被称为 随机存取(随机存取),运行时间为O(1)
(无论输入数据的大小如何,操作所需的时间不变,即无论数组有多大,按索引访问元素所需的时间相同)。 -
在末尾高效添加和删除 - 如果需要经常在数组末尾添加或删除元素,那么可以使用`push!
(添加一个元素)和`pop!
(删除最后一个元素)函数。这些操作速度非常快,因为它们不需要重新排列其他元素。 -
处理固定数量的数据 - 如果事先知道要存储多少个元素,数组的工作效率会特别高。数组可以一次性分配内存,从而将调整大小的开销降至最低。
-
矢量计算和线性代数 - Julia 优化了数组,尤其是数值数组的处理。如果要处理矩阵、向量或执行线性代数运算,数组是最佳选择。例如,处理矩阵的函数(如
dot
、cross
)或通过broadcast 进行的数组运算(.+
、.*
)在数组中运行速度更快。
不推荐使用数组时
-
频繁添加或删除中间或开头的元素 - 如果需要频繁插入或删除数组开头或中间的元素。例如,"insert!"(插入)和 "deleteat!"(删除)函数需要移动所有后续元素,因此对于大型数组来说效率很低。替代方法:使用
DataStructures.jl
包中的Deque
函数,它对两端的插入和删除进行了优化。 -
按键查找项目 - 如果需要按唯一键(如用户名或 ID)快速查找项目,则不适合使用数组,因为数组是通过搜索所有项目来查找的(这相当耗时)。替代方法:使用
Dict
(字典),它可以让你按键快速查找项目。 -
频繁调整结构大小--如果数据结构频繁变化(例如,在任意位置添加或删除元素),数组就会失效。替代方法:考虑使用link:https://juliacollections.github.io/DataStructures.jl/latest/ 包 [DataStructures.jl] 中的
Set
、Dict
或列表结构(如List
)。 -
使用不可变数据 - Julia 中的数组是可变的,这意味着它们的元素在创建后可以更改。如果需要不可变的数据结构,最好使用元组(Tuple)。元组不允许更改元素,因此便于存储恒定数据。例如
t = (1, 2, 3) t[1] = 10 # 搞错了! 元组是不可变的
字符串和符号的数组、矩阵和向量
在 Julia 中,数组、矩阵和向量可以像数字一样包含字符串或符号。
# 字符串的向量
v = ["apple", "banana", "cherry"]
println(v[1]) # 苹果
# 符号矩阵
m = ['a' 'b'; 'c' 'd']
println(m[1, 2]) # b
# 字符串的三维数组
a = [["a", "b"], ["c", "d"]]
println(a[1][2]) # "b"
-
字符串向量—每个字符串都作为一维数组的一个元素存储。
-
字符矩阵 - 一个二维数组,每个元素都是一个字符。
-
字符串三维数组 - 支持文本数据的任意维数。
Julia 中的数组默认为动态数组,但其长度是固定的,除非明确更改。这意味着创建数组时,其当前长度保持不变,直到使用了 "push!"、"append!"或 "resize!"等特殊方法。这样做是为了提高性能:固定长度的操作不需要不断重新分配内存,因此速度更快。
带有 end
的更复杂的结构(如 end+1
)不允许直接为数组元素赋值,因为此类操作超出了当前长度。要添加新元素,必须明确地增加数组的大小。例如,可以使用 resize!
:
a = [10, 20, 30]
resize!(a, length(a) + 1) # 将数组扩展1个元素
a[end] = 40 # 为新元素赋值
println(a) # [10, 20, 30, 40]
带有 end
的构造可用于对现有数组边界内的索引进行索引和算术运算。例如
-
a[end]
- 访问最后一个元素。 -
a[end-1]
- 访问从末尾开始的第二个元素。 -
a[1:end]
- 访问整个数组。 -
a[1:end-1]
- 获取除最后一个元素以外的所有元素。
在 Julia 中,不能直接为索引为 end+1 的元素赋值,也不能为超出数组当前长度的任何索引赋值。要调整数组的大小,必须使用特殊的方法,如`resize!、`push!`或`append! 。
|
数组索引
Julia 中的索引从 1 开始,即数组的第一个元素的索引为 1
。数组还支持特殊的 "end "索引,表示数组的最后一个元素或相应维度的大小。索引示例
a = [10, 20, 30, 40]
println(a[1]) # 10是第一个元素
println(a[end]) # 40是最后一个元素
# 矩阵的索引
m = [1 2; 3 4]
println(m[1, end]) # 2-第一行的最后一个元素
-
索引
1
总是指向第一个元素。 -
end "用于指一维和多维数组中的最后一个元素。
-
对于矩阵,第一个索引是行号,第二个索引是列号。
*其他索引变化:
-
使用范围可以同时引用数组中的多个元素:
b = [1, 2, 3, 4, 5] println(b[2:end]) # [2, 3, 4, 5] — 从第二个到最后一个的元素
-
以特定增量选择元素:
b = [1, 2, 3, 4, 5] println(b[1:2:end]) # [1,3,5]-以2为增量的元素
在 Julia 中,"1:2:end "形式的表达式称为范围。范围是一种结构,它描述了一个有起点、级数和终点的数字序列。例如,
1:2:5`创建了一个以 2 为增量从 1 到 5 的序列:
[1, 3, 5]`。在这个例子中,
1:2:end
表示选择从数组的第一个元素(1
)开始,以增量`2`进行,并以数组的最后一个元素(end
)结束。使用范围可以方便有效地处理数组和矩阵索引。使用范围的示例# 简单范围 r1 = 1:5 # 创建序列[1, 2, 3, 4, 5] r2 = 1:2:10 # 创建序列[1, 3, 5, 7, 9] # 使用范围进行索引 a = [10, 20, 30, 40, 50] println(a[2:4]) # [20, 30, 40] println(a[1:2:end]) # [10, 30, 50] # 范围与"结束" println(a[3:end]) # [30, 40, 50]
-
选择符合特定条件的项目(详见章节)。 逻辑索引):
b = [1, 2, 3, 4, 5] println(b[b .> 3]) # [4,5]-大于3的元素
在 Julia 中,不仅可以直接设置范围,还可以使用变量来设置范围。例如
arr = [10, 20, 30, 40, 50]
a = 1:2:5 # 在变量中设置范围
println(arr[a]) # [10, 30, 50]
逻辑索引
Julia 中的逻辑索引允许您根据返回与原始数组维度相同的逻辑数组(true
/false
)的条件来选择数组元素。这种方法对于过滤数据非常有用。
+ 示例
# 原始数组
a = [10, 20, 30, 40, 50]
# 条件:选择超过25个项目
filtered = a[a .> 25] # [30, 40, 50]
println(filtered)
# 条件:元素是10的倍数
filtered = a[a .% 10 .== 0] # [10, 20, 30, 40, 50]
println(filtered)
这里
-
条件前的
.
运算符(.>
,.==
)表示分段运算。关于此类运算的更详细说明,请参阅矢量化和逻辑索引 一文。 -
逻辑数组 `[false,false,true,true,true]`将用于选择相应的元素。
多维数组示例:
# 2x3矩阵
m = [1 2 3; 4 5 6]
# 条件:选择大于3的项目
filtered = m[m .> 3] # [4, 5, 6]
println(filtered)
处理数组、向量和矩阵的操作符
Julia 提供的操作符可以简化数组的处理,无论是一维数组(矢量)、二维数组(矩阵)还是更复杂的多维数组结构。以下是用于索引、转换和组合数据的主要操作符:
-
操作符
:
用于沿特定维度选择数组的所有元素或创建范围。例如
# 原始数组 m = [1 2 3; 4 5 6; 7 8 9] # 第一行的所有元素 println(m[1, :]) # [1, 2, 3] # 第二列的所有元素 println(m[:, 2]) # [2, 5, 8] # 指数范围 println(m[1:2, 1:2]) # [1 2; 4 5]
-
''''运算符用于连接 (实数的转置)。它只适用于数字数组。
例如
# 向量字符串 v = [1 2 3] # 转置到列 println(v') # [1; 2; 3;;]
要使用复数对数组进行换置, '
运算符也会返回复数共轭结果。如果只需要行到列的转置(不需要共轭),请使用transpose
函数。
要对包含非数字元素(如字符串或用户定义类型)的数组进行换置,请使用 如果将
|
-
操作符
;
用于纵向组合数组,即添加行。例如
# 源代码 a = [1, 2, 3] b = [4, 5, 6] # 垂直连接 result = [a; b] println(result) # 6元素向量{Int64}:[1; 2; 3; 4; 5; 6]
使用 for
对数组进行遍历
在 Julia 中,使用 for
循环可以方便地遍历数组。让我们来看看通过 for
处理数组的基本方法。以下语法用于循环遍历数组中的所有元素:
arr = [10, 20, 30, 40]
for x in arr
println(x)
end
输出
10
20
30
40
有时不仅需要获取值,还需要获取它们的索引。为此,可以使用 eachindex
自动选择最佳索引方法:
arr = ["a", "b", "c"]
for i in eachindex(arr)
println("元素 ", i, ": ", arr[i])
end
输出
元素1:a
元素2:b
元素3:c
另一种获取索引和值的方法是使用`enumerate`函数:
arr = ["apple", "banana", "cherry"]
for (i, val) in enumerate(arr)
println("$i: $val")
end
输出
1: apple
2: banana
3: cherry
对于多维数组,可以使用嵌套循环:
matrix = [1 2 3; 4 5 6]
for i in 1:size(matrix, 1) # 穿过线
for j in 1:size(matrix, 2) # 穿过柱子
println("matrix[$i, $j] = ", matrix[i, j])
end
end
输出
matrix[1, 1] = 1
matrix[1, 2] = 2
matrix[1, 3] = 3
matrix[2, 1] = 4
matrix[2, 2] = 5
matrix[2, 3] = 6
您可以直接使用 for
代替嵌套循环:
matrix = [1 2 3; 4 5 6]
for x in matrix
println(x)
end
输出
1
4
2
5
3
6
Julia 按列而不是按行遍历元素。要按行绕过数组,可以使用`permutedims`或`eachrow`:
for row in eachrow(matrix)
println(row)
end
输出
[1, 2, 3]
[4, 5, 6]