这一部分介绍Julia的向量、元组、集合、字典等复合数据结构,
以及函数的进一步介绍。
一维数组
Julia支持一维和多维的数组,
当一维数组的元素是数值时,
也可以理解成数学中的向量。
在程序中直接定义一个向量,
只要用方括号内写多个逗号分隔的数值,如
v1 = [2, 3, 5, 7, 11, 13, 17]
7-element Vector{Int64}:
2
3
5
7
11
13
17
4-element Vector{Float64}:
1.5
3.0
4.0
9.12
其中v1是整数型的向量, v2是浮点型Float64的向量。
也可以定义元素为字符串的数组,
元素为不同类型的数组,
等等:
3-element Vector{String}:
"苹果"
"桔子"
"香蕉"
v4 = [123, 3.14, "数学", [1, 2, 3]]
4-element Vector{Any}:
123
3.14
"数学"
[1, 2, 3]
用length(x)
求向量x
的元素个数,如
可以用1:5
定义一个范围,
在仅使用其中的元素值而不改写时作用与[1, 2, 3, 4, 5]
类似。1:2:9
定义带有步长的范围,表示的值与[1, 3, 5, 7, 9]
类似。
范围只需要存储必要的开始、结束、步长信息,
所以更节省空间,
但是不能对其元素进行修改。
1:5
## 1:5
1:2:7
## 1:2:7
5:-1:1
## 5:-1:1
范围不是向量,
而是一种“可遍历数据结构”。
用collect()
函数可以将范围转换成向量,如:
5-element Vector{Int64}:
5
4
3
2
1
向量下标
若x
是向量,i
是正整数,x[i]
表示向量的第i
个元素。
第一个元素的下标为1,这种规定与R、FORTRAN语言相同,
但不同于Python、C、C++、JAVA语言。
如
v1 = [2, 3, 5, 7, 11, 13, 17]
7-element Vector{Int64}:
2
3
5
7
11
13
17
用end
表示最后一个元素位置,如:
对元素赋值将在原地修改元素的值,如
v1[2] = 0
@show v1;
## v1 = [2, 0, 5, 7, 11, 13, 17]
这说明数组是“可变类型”(mutable),
即其中的成分可以原地修改。
字符串和元组则属于不可变类型(immutable)。
@show expr
可以用比较简洁的带有提示的方式显示表达式和表达式的值。
用范围作为下标
下标可以是一个范围,如
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[2:4]
3-element Vector{Int64}:
3
5
7
在这种范围中,用end
表示最后一个下标,如
4-element Vector{Int64}:
7
11
13
17
4-element Vector{Int64}:
2
3
5
7
4-element Vector{Int64}:
2
5
11
17
7-element Vector{Int64}:
17
13
11
7
5
3
2
实际上,reverse(x)
可以返回次序颠倒后的数组。
可以用仅有冒号作为下标,
这时表示包含所有元素的子集。
取出的多个元素可以修改,
可以用.=
运算符赋值为同一个标量,如:
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[:] .= 0;
@show v1;
## v1 = [0, 0, 0, 0, 0, 0, 0]
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[1:3] .= 0
@show v1;
## v1 = [0, 0, 0, 7, 11, 13, 17]
也可以分别赋值,如
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[1:3] = [101, 303, 505];
@show v1;
## v1 = [101, 303, 505, 7, 11, 13, 17]
数组类型
当数组元素都是整数时,
显示其类型为“Array{Int64}
”,
常用的还有“Array{Float64}
”,
“Array{String}
”,
“Array{Any}
”等,
“Any
”是Julia语言类型系统的根类型,
相应的数组可以容纳任何Julia对象作为元素。
如果数组元素都是基本类型如Float64,
则不允许给元素赋值为其它类型,如:
3-element Vector{Float64}:
1.2
2.5
3.6
vf[2] = "abc"
## MethodError: Cannot `convert` an object of type String to an object of type Float64
## .........
用eltype()
求元素类型,如:
向量初始化
用zeros(n)
可以生成元素类型为Float64、元素值为0、长度为n的向量,如
3-element Vector{Float64}:
0.0
0.0
0.0
用zeros(Int64, 3)
可以生成指定类型的(这里是Int64)初始化向量。如
3-element Vector{Int64}:
0
0
0
用Vector{Float64}(undef, n)
可以生成元素类型为Float64的长度为n的向量,
元素值未初始化,如
Vector{Float64}(undef, 3)
3-element Vector{Float64}:
1.40319995e-315
1.40320011e-315
1.40320027e-315
类似可以生成其它元素类型的元素值未初始化向量,如
y1 = Vector{Int}(undef, 3);
用这样的办法为向量分配存储空间后可以随后再填入元素值。
可以用fill!()
填入统一的值。
函数名以!
结尾是一个习惯用法,
表示该函数会修改其第一自变量的值。
如:
3-element Vector{Int64}:
100
100
100
也可以用fill(value, n)
生成一个元素值都等于value
的长度为n
的一维数组。
可以用collect()
将一个范围转换成可修改的向量。如:
5-element Vector{Int64}:
1
2
3
4
5
变量与值
由于Julia的变量仅仅是向实际存储空间的引用(reference),
或称绑定(binding),
所以两个变量可以引用(绑定)到同一个向量的存储空间,
修改了其中一个变量的元素值,则另一个变量的元素也被修改了。
如
x1 = [1,2,3]
x2 = x1
x2[2] = 100
@show x1;
## x1 = [1, 100, 3]
用“===
”或“≡
”(\equiv
+TAB)可以比较两个变量是否同一对象,
如:
允许两个变量指向同一个对象是有用的,
尤其在函数自变量传递时,
但是在一般程序中这种作法容易引起混淆。
向量(或者数组)作为函数自变量时,
调用函数时传递的是引用,
在函数内可以修改传递进来的向量的元素值。
如果需要制作数组的副本,
用copy()
函数。
如
x1 = [1,2,3]
x2 = copy(x1)
x2[2] = -100
@show x1;
## x1 = [1, 2, 3]
x2 === x1
## false
将仅有冒号的子集如x[:]
放在等号左边可以修改所有元素,
如果将其放在等号右边并赋值给一个变量,
就可以制作副本,如:
x1 = [1,2,3]
x2 = x1[:]
x2[2] = -100
@show x1;
## x1 = [1, 2, 3]
Julia对象的这种引用或者绑定做法,
初学者比较容易用错。
例如,
下面的程序将一个数组嵌套在另一个数组中:
x0 = [3, 4]
x1 = [1,2, x0]
x1[3][1] = 333
@show x0;
## x0 = [333, 4]
因为x1
中引用(绑定)了x0
的值,
所以x1[3]
和x0
共用同一存储,
修改了x1[3]
就修改了x0
。
那么,
制作x1
的副本能否解决问题?
x0 = [3, 4]
x1 = [1, 2, x0]
x2 = copy(x1)
x2[1] = 111
x2[3][1] = 333
@show x2;
## x2 = Any[111, 2, [333, 4]]
@show x1;
## x1 = Any[1, 2, [333, 4]]
@show x0;
## x0 = [333, 4]
虽然x1[1]
没有被修改,但是x1[3][1]
还是被修改了,x0
也被修改了。
这是因为copy()
执行的是所谓“浅层复制”,
对于内嵌的对象仍为引用。
可以用deepcopy()
,
能解决大部分问题:
x0 = [3, 4]
x1 = [1,2, x0]
x2 = deepcopy(x1)
x2[1] = 111
x2[3][1] = 333
@show x2;
## x2 = Any[111, 2, [333, 4]]
@show x1;
## x1 = Any[1, 2, [3, 4]]
@show x0;
## x0 = [3, 4]
仅修改了x2
,没有修改x1
和x0
。
向量的有关函数
为了判断元素x
是否属于数组v
,可以用表达式x in v
或x ∈ v
判断,
结果为布尔值。
函数indexin(a, b)
返回向量a
的每个元素首次出现在b
中的位置,
没有时返回nothing
,如:
indexin([1,3,5,3], [1,2,3])
4-element Array{Union{Nothing, Int64},1}:
1
3
nothing
3
若v
是向量,x
是一个元素,push!(v, x)
修改向量v
,
将x
添加到向量v
的末尾。pushfirst!(v, x)
修改向量v
,
将x
添加到向量v
的开头,原有的元素后移。
注意,
函数名以叹号结尾是一个习惯约定,
表示此函数会修改其第一个自变量。
insert!(v, k, xi)
函数可以在向量v
的指定下标k
位置插入指定的一个元素,
原有的元素后移。
如
v3 = [2,3,5]
push!(v3, 7)
@show v3;
## v3 = [2, 3, 5, 7]
pushfirst!(v3, 1)
@show v3;
## v3 = [1, 2, 3, 5, 7]
若v
是向量,u
也是一个向量,append!(v, u)
修改向量v
,
将u
的所有元素添加到向量v
的末尾。
要注意append!
和push!
的区别,
一个是添加一个向量的所有元素到末尾,
一个是添加一个元素到末尾。
如
v3 = [2,3,5]
append!(v3, [7,11])
@show v3;
## v3 = [2, 3, 5, 7, 11]
pop!(v)
可以返回v
的最后一个元素并从v
中删除此元素。popfirst!(v)
类似。splice!(v, k)
函数可以返回指定下标位置的元素并从v
中删除此元素,deleteat!(v, k)
函数可以v
中删除指定下标位置的元素但不返回值。empty!(x)
可以情况数组的所有元素,
实际上,这个函数可以情况集合、字典等复合类型的元素。
注意,push!()
等函数修改输入的向量的大小,
根据使用的环境,
这可能是很高效的做法,
但是数值计算程序中通常不修改数组大小,
而是预先分配好数组的大小。
如果确实无法预先确定数组大小,
又有运行效率的困扰,
可以用如sizehint!(x, 10000)
这样的做法为数组预先提示一个大小,
这可以提高程序的效率。
replace!()
函数可以用来在数组中替换元素,如:
x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0)
@show x;
## x = [0, 2, 0, 4, 0]
x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0, 4 => 3)
@show x;
## x = [0, 2, 0, 3, 0]
可以指定一个总替换次数的上限,如:
x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0, 4 => 3, count = 2)
@show x;
## x = [0, 2, 0, 4, 1]
如果要合并两个一维数组并将结果生成一个新数组,
不修改原来的两个数组,
可以用vcat()
函数,如:
v1 = [1,2]; v2 = [-2, -1]
v3 = vcat(v1, v2)
@show v3;
## v3 = [1, 2, -2, -1]
v3[1] = 111
@show v1;
## v1 = [1, 2]
filter!(f, x)
指定一个示性函数f
,将x
中不满足条件的元素删除,
如:
x = [2, 3, 5, 7, 11, 13]
filter!(a -> a % 3 == 1, x)
show(x)
## [7, 13]
unique(v)
返回去掉重复元素的结果,unique!(v)
则直接去掉v
中的重复元素。
sort(v)
返回向量v
按升序排序的结果;sort!(v)
直接修改v
,将其元素按升序排序。
如果要用降序排序,可以加选项rev=true
。sortperm(v)
返回将v
的元素从小到大排序所需要的下标序列,
在多个等长向量按照其中一个的次序同时排序时此函数有用。
maximum(v)
求最大值,minimum(v)
求最小值,argmax(v)
求最大值首次出现的下标,argmin(v)
求最大值首次出现的下标。findmax(v)
和findmin(v)
则返回最值和相应的首次出现的下标。
sum(v)
求和,prod(v)
求乘积。
对于布尔型数组,all(v)
判断所有元素为真,any(v)
判断存在真值元素。
用x == y
可以比较两个等长数组的对应元素是否完全相同。
对于字符串x
,
可以用collect(x)
将其转换为每个字符为一个元素的数组。
对于元组,
也可以用collect
转换成数组。
广播
许多现代的数据分析语言,
如Python, Matlab, R等都存在循环的效率比编译代码低一两个数量级的问题,
在这些语言中,
如果将对向量和矩阵元素的操作向量化,
即以向量和矩阵整体来执行计算,
就可以利用语言内建的向量化计算获得与编译代码相近的执行效率。
Julia语言依靠其LLVM动态编译功能,
对向量和矩阵元素循环时不损失效率,
用显式循环处理向量、矩阵与向量化做法效率相近,
有时显式循环效率更高。
但是,向量化计算的程序代码更简洁。
Julia中的函数,
包括自定义函数,
如果可以对单个标量执行,
将函数名加后缀句点后,
就可以变成向量化版本,
对向量和矩阵执行。
这称为广播。
运算也是如此,运算符前面加点后就可以将标量运算应用到元素之间的运算。
如
3-element Array{Float64,1}:
1.0
1.4142135623730951
1.7320508075688772
这种向量化对于多个自变量的函数也成立。
通过编译优化,
可以达到专用的以向量作为输入的函数的效率,
如果同一个语句中有多个加点运算,
编译时能够将其尽可能地合并到同一循环中。
元组(Tuple)
概念和生成
与向量类似的一种数据类型称为元组(tuple)。
如
(1, 2, 3)
## (1, 2, 3)
(1, "John", 5.1)
## (1, "John", 5.1)
元组的元素不要求属于同一类型。
单个元素的元组要有逗号分隔符,如(1,)
是单个元素的元组,
而(1)
不是元组。
元组表面上类似于一维数组,
但是元组属于不可修改(immutable)类型,
不能修改其中的元素。
其存储也与数组不同。
可以用tuple()
函数生成元组。
可以用类似一维数组的方法对元组取子集,
如x[1]
, x[2:3]
等。
如:
x = ('a', 'b', 'c', 'd')
typeof(x)
## NTuple{4,Char}
这个类型的意思是由4个字符组成的元组。
访问片段
x[1]
## 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
x[2:3]
## ('b', 'c')
不允许修改元组中的元素:
结果出错:
MethodError: no method matching setindex!(::NTuple{4,Char}, ::Char, ::Int64)
.........
比较
元组可以看作一个整体参加比较,
比较方法类似于字典序,如:
(1, 3, 5) < (1, 3, 6)
## true
赋值等号左边的元组
可以利用元组写法对变量同时赋值,
如
a, b = 13, 17
println("a=", a, " b=", b)
## a=13 b=17
这种赋值可以用来交换两个变量的值,如:
a, b = b, a
println("a=", a, " b=", b)
## a=17 b=13
元组赋值的右侧也可以是数组等其它序列类型,如
a, b = [19, 23]
println("a=", a, " b=", b)
## a=19 b=23
用元组在函数中返回多个值
自定义函数可以返回元组,
从而返回多个值,见下面的自定义函数章节。
内置的有些函数就利用了这种特性,
比如,divrem(x, y)
返回除法的商和余数:
元组转换为一维数组
用collect()
将元组转换为一维数组,如:
3-element Array{Int64,1}:
1
3
5
两个向量成对使用
设x
和y
是两个等长的一维数组,zip(x, y)
是一个迭代器(可以用在for循环中),
每次迭代返回两个数组的一对对应元素。
可以用collect()将迭代器转换成二元组的数组:
x = ["a", "b", "c"]
y = [3, 1, 2]
collect(zip(x, y))
3-element Vector{Tuple{String, Int64}}:
("a", 3)
("b", 1)
("c", 2)
有名元组
元组可以为元素命名,
这使得其在一定程度上类似于字典。
但是,字典是可变类型(可以修改其中的元素),
元组是不可变类型。
如
tn1 = (; name="John", age=32)
tn1[:name]
## "John"
tn1[1]
## "John"
定义时用了左括号后面加一个分号的格式。
这是推荐的写法,
当有多个元素时可以省略分号,
但有分号使得定义有名元组的意图更明显。
有名元组经常用来给函数的关键字参数赋值,
而这样的关键字参数的值又是类似关键字参数的,
即内容可有可无,可多可少。
要注意有名元组用变量名访问时用的是符号(Symbol),
即不写成字符串的变量名前面有冒号。
也可以用加点格式访问:
字典
生成字典
Julia提供了一种Dict数据类型,
是映射的集合,
每个元素是从一个“键”(key)到另一个“值”(value)的映射,
元素之间没有固定次序。如
d = Dict("name" => "Li Ming", "age" => 18)
Dict{String,Any} with 2 entries:
"name" => "Li Ming"
"age" => 18
用Dict()
生成空的字典。
也可以用二元组的数组作为初值定义字典,如
d2orig = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
d2 = Dict(d2orig)
Dict{Char,Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
当键和值分别保存在两个等长的数组中的时候,
可以用zip()
函数将这两个数组合并为二元组的数组,
从而产生字典,如:
x = ['a', 'b', 'c', 'd']
y = [1,2,3,4]
d2 = Dict(zip(x, y))
Dict{Char, Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
用length()
求长度,如:
字典的键可以用字符串、整数值、浮点数值、元组这样的不可变类型(immutable),
不能取数组这样的可变类型(mutable)。
最常用的是字符串。
上面生成字典的方法是自动判断键和值的数据类型,
为保险起见,
最好在生成字典时指定键和值的数据类型,
格式为Dict{S, T}(...)
,S
为键的类型,T
为值的类型。如:
Dict{String, Int64}("apple" => 1, "pear" => 2, "orange" => 3)
Dict{String,Int64} with 3 entries:
"pear" => 2
"orange" => 3
"apple" => 1
对(Pair)
事实上,"apple" => 1
这样的写法也是Julia的一种数据类型,
称为“对”(Pair)。
如:
x = "apple" => 1
typeof(x)
## Pair{String, Int64}
用first(x)
取出对的第一项,
用last(x)
取出对的第二项。如:
(first(x), last(x))
## ("apple", 1)
可以用collect(dict)
将字典转换成键、值二元组的一维数组,如:
4-element Vector{Pair{Char, Int64}}:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
访问元素
访问单个元素如
d = Dict("name" => "Li Ming", "age" => 18)
d["age"]
## 18
这种功能类似于R语言中用元素名作为下标,
但R中还可以用序号访问元素,
而字典中的元素没有次序,不能用序号访问。
读取字典中单个键的对应值也可以用get(d, key, default)
的格式,
其中default
是元素不存在时的返回值。如:
可以用haskey(d, key)
检查某个键值是否存在,如:
haskey(d, "gender")
## false
给不存在的键值赋值就可以增加一对映射,如
d["gender"] = "Male";
@show d;
## d = Dict{String, Any}("name" => "Li Ming",
## "gender" => "Male", "age" => 18)
delete!(d, key)
可以删除指定的键值对。
get!(d, key, default)
可以在指定键值不存在时用default
值填入该键值,
已存在时就不做修改,
两种情况下都返回新填入或原有的键对应的值。
pop!(d, key)
返回key
对应的值并从字典中删除该键值对。
merge(dict1, dict2)
合并两个字典,
有共同键时取后一个字典的值。
遍历字典
可以用keys()
函数遍历各个键值,次序不确定:
d2 = Dict('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4)
for k in keys(d2)
println(k, " => ", d2[k])
end
a => 1
c => 3
d => 4
b => 2
除了可以用haskey(dict, key)
判断某个键是否存在,
也可以用key in keys(dict)
判断,如:
在字典中查找某个键是使用散列表(hash table)技术,
所以查找时间不会随元素个数增长而线性增长,
可以比较方便地存储需要快速查找的键值对。
字典存储并没有固定的存储次序。
为了在遍历时按键值的次序,
需要使用如下的效率较低的方法:
for k in sort(collect(keys(d2)))
println(k, " => ", d2[k])
end
a => 1
b => 2
c => 3
d => 4
对字典排序遍历的另一方法是将字典转换成键值对的数组,
然后用sort
排序,
再遍历,如:
d2p = collect(d2)
sort!(d2p, by=first)
for (k, v) in d2p
println(k, " ==> ", v)
end
a ==> 1
b ==> 2
c ==> 3
d ==> 4
可以用values()
遍历各个值,但也没有固定次序。比如
4-element Array{Int64,1}:
1
3
4
2
可以直接用二元组对字典遍历,如
for (k,v) in d2
println(k, " => ", v)
end
a => 1
c => 3
d => 4
b => 2
可以将字典转换成键值对的列表,
如:
[(k, v) for (k, v) in d2]
4-element Vector{Tuple{Char, Int64}}:
('a', 1)
('c', 3)
('d', 4)
('b', 2)
用生成器生成字典
可以用Dict(x => f(x) for x in collection)
的方法生成字典,
如:
Dict(x => x*x for x in [2,3,5,7])
Dict{Int64,Int64} with 4 entries:
7 => 49
2 => 4
3 => 9
5 => 25
字典应用:频数表
在基本的描述统计中,
经常需要对某个离散取值的变量计算其频数表,
即每个不同值出现的次数。
如果不利用字典类型,
可以先找到所有的不同值,
将每个值与一个序号对应,
然后建立一个一维数组计数,
每个数组元素与一个变量值对应。
利用字典,
我们不需要预先找到所有不同值,而是直接用字典计数,
每个键值是一个不同的变量值,
每个值是一个计数值。
对字典可以用get()
函数提取某个键值对应的值,
并在键值不存在时返回指定的缺省值。
如:
sex = ["F", "M", "M", "F", "M"]
freqs = Dict()
for xi in sex
freqs[xi] = get(freqs, xi, 0) + 1
end
freqs
Dict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
将上述的频数计算功能编写成一个函数如下:
function freqd(x)
y = Dict()
for xi in x
y[xi] = get(y, xi, 0) + 1
end
return y
end
freqd(sex)
Dict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
StatsBase包的countmap
函数实现了上述的freqd
的功能。
如:
using StatsBase
StatsBase.countmap(sex)
Dict{String, Int64} with 2 entries:
"M" => 3
"F" => 2
可以看出StatsBase的版本更为合理,
其返回的字典的数据类型更加精确。
有时返回字典类型不方便使用,
可以返回取值和频数分别的列表:
function freq(x)
y = StatsBase.countmap(x)
return keys(y), values(y)
end
freq(sex)
## (["M", "F"], [3, 2])
d3 = freq("disillusionment")
d3
## (['n', 'd', 'i', 's', 'l', 'u', 'o', 'm', 'e', 't'],
## [2, 1, 3, 2, 2, 1, 1, 1, 1, 1])
因为字典的键必须是不可变类型,
所以freq()
中数组x的元素必须是不可变类型。
集合类型
Julia中Set是集合类型。
集合是可变类型,
没有重复元素,
元素没有次序。
用Set()
生成一个集合,如:
Set{Int64} with 3 elements:
2
3
1
Set{Int64} with 3 elements:
2
3
1
Set(['a', 'b', 'c', 'b'])
Set{Char} with 3 elements:
'a'
'c'
'b'
Set{Char} with 3 elements:
'k'
'e'
'p'
Set()
输入一个序列(字符串也是序列),
将序列的元素变成集合元素。
注意Set([1,2,3])
正确而Set(1,2,3)
错误。
因为字符串也是序列,
所以,
要生成只有一个字符串的集合,
也需要将其作为字符串的数组输入,如:
Set{String} with 1 element:
"keep"
支持集合的常见运算:
union(A, B)
或A ∪ B
:并集。∪
输入为\cup<TAB>
。intersect(A, B)
或A ∩ B
: 交集。∩
输入为\cap<TAB>
。setdiff(A, B)
或A \ B
:差集。symdiff(A, B)
:对称差集,即 (A∖B)∪(B∖A)。
issetequal(A, B)
: 集合相等。issubset(A, B)
或A ⊆ B
:子集,⊆
输入为\subseteq<TAB>
。⊈
(\nsubseteq
+TAB)表示非子集。⊇
(\supseteq<TAB>
):超集。⊉
(\nsupseteq<TAB>
)表示非超集。- 属于关系用
in
,∈
(\in
+TAB),∋
(\ni
+TAB),∉
(\notin
+TAB),∌
(\nni
+TAB)表示。
例如,
判断某个单词的字母都在另一个单词的字母中:
Set("cat") ⊆ Set("atomic")
## true
判断某个单词中有没有重复字母:
length(Set("keep")) < length("keep")
## true
push!(x, a)
将元素a加入到集合x中。
对数组x
, unique(x)
返回由x
的不同元素组成的数组。
自定义复合数据类型
类似于其它编程语言中的struct, class,
Julia可以用mutable struct或者struct定义自己的复合数据类型。
例如,
为了表示平面上的一个矩形,
我们需要一个左下角坐标和长度、高度,
就可以定义如下的数据类型:
mutable struct Rectangle
xll::Real
yll::Real
width::Real
height::Real
end
这定义了一个新的数据类型Rectangle
。
命名的惯例是使用大写字母开头。
其中的xll
, yll
, width
, height
称为这个复合数据结构的“属性”。
生成这个类型的变量:
rect1 = Rectangle(0, 0, 2, 1)
这表示左下角坐标为
(0,0),宽度为2,高度为1的一个矩形。
用变量名.属性名
的格式访问其中的属性,如:
rect1 = Rectangle(0, 0, 2, 1)
rect1.width
## 2
可以针对这样的自定义类型定义相应的运算和函数,
比如,
平移运算的函数:
function move(rect::Rectangle, offset)
Rectangle(rect.xll + offset[1],
rect.yll + offset[2],
rect.width, rect.height)
end
测试:
rect2 = move(rect1, (20, 10))
## Rectangle(20, 10, 2, 1)
用struct
定义的复合数据,
其中的属性不允许修改。
用mutable struct
定义的复合数据,
其中的属性是可以修改的。
韭菜热线原创版权所有,发布者:风生水起,转载请注明出处:https://www.9crx.com/75043.html