2 复合数据结构
这一部分介绍Julia的向量、元组、集合、字典等复合数据结构, 以及函数的进一步介绍。
2.1 一维数组
Julia支持一维和多维的数组, 当一维数组的元素是数值时, 也可以理解成数学中的向量。
在程序中直接定义一个向量, 只要用方括号内写多个逗号分隔的数值,如
= [2, 3, 5, 7, 11, 13, 17] v1
7-element Vector{Int64}:
2
3
5
7
11
13
17
= [1.5, 3, 4, 9.12] v2
4-element Vector{Float64}:
1.5
3.0
4.0
9.12
其中v1是整数型的向量, v2是浮点型Float64的向量。 也可以定义元素为字符串的数组, 元素为不同类型的数组, 等等:
= ["苹果", "桔子", "香蕉"] v3
3-element Vector{String}:
"苹果"
"桔子"
"香蕉"
= [123, 3.14, "数学", [1, 2, 3]] v4
4-element Vector{Any}:
123
3.14
"数学"
[1, 2, 3]
用length(x)
求向量x
的元素个数,如
length(v1)
## 7
可以用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()
函数可以将范围转换成向量,如:
collect(5:-1:1)
5-element Vector{Int64}:
5
4
3
2
1
2.1.1 向量下标
若x
是向量,i
是正整数,
x[i]
表示向量的第i
个元素。
第一个元素的下标为1,这种规定与R、FORTRAN语言相同,
但不同于Python、C、C++、JAVA语言。
如
= [2, 3, 5, 7, 11, 13, 17] v1
7-element Vector{Int64}:
2
3
5
7
11
13
17
2]
v1[## 3
用end
表示最后一个元素位置,如:
end]
v1[## 17
对元素赋值将在原地修改元素的值,如
2] = 0
v1[@show v1;
## v1 = [2, 0, 5, 7, 11, 13, 17]
这说明数组是“可变类型”(mutable), 即其中的成分可以原地修改。 字符串和元组则属于不可变类型(immutable)。
@show expr
可以用比较简洁的带有提示的方式显示表达式和表达式的值。
2.1.1.1 用范围作为下标
下标可以是一个范围,如
= [2, 3, 5, 7, 11, 13, 17]
v1 2:4] v1[
3-element Vector{Int64}:
3
5
7
在这种范围中,用end
表示最后一个下标,如
4:end] v1[
4-element Vector{Int64}:
7
11
13
17
1:(end-3)] v1[
4-element Vector{Int64}:
2
3
5
7
1:2:7] v1[
4-element Vector{Int64}:
2
5
11
17
end:-1:1] v1[
7-element Vector{Int64}:
17
13
11
7
5
3
2
实际上,reverse(x)
可以返回次序颠倒后的数组。
可以用仅有冒号作为下标,
这时表示包含所有元素的子集。
取出的多个元素可以修改,
可以用.=
运算符赋值为同一个标量,如:
= [2, 3, 5, 7, 11, 13, 17]
v1 :] .= 0;
v1[@show v1;
## v1 = [0, 0, 0, 0, 0, 0, 0]
= [2, 3, 5, 7, 11, 13, 17]
v1 1:3] .= 0
v1[@show v1;
## v1 = [0, 0, 0, 7, 11, 13, 17]
也可以分别赋值,如
= [2, 3, 5, 7, 11, 13, 17]
v1 1:3] = [101, 303, 505];
v1[@show v1;
## v1 = [101, 303, 505, 7, 11, 13, 17]
2.1.2 数组类型
当数组元素都是整数时,
显示其类型为“Array{Int64}
”,
常用的还有“Array{Float64}
”,
“Array{String}
”,
“Array{Any}
”等,
“Any
”是Julia语言类型系统的根类型,
相应的数组可以容纳任何Julia对象作为元素。
如果数组元素都是基本类型如Float64, 则不允许给元素赋值为其它类型,如:
= [1.2, 2.5, 3.6]; vf vf
3-element Vector{Float64}:
1.2
2.5
3.6
2] = "abc"
vf[## MethodError: Cannot `convert` an object of type String to an object of type Float64
## .........
用eltype()
求元素类型,如:
eltype(vf)
## Float64
2.1.3 向量初始化
用zeros(n)
可以生成元素类型为Float64、元素值为0、长度为n的向量,如
zeros(3)
3-element Vector{Float64}:
0.0
0.0
0.0
用zeros(Int64, 3)
可以生成指定类型的(这里是Int64)初始化向量。如
zeros(Int64, 3)
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
类似可以生成其它元素类型的元素值未初始化向量,如
= Vector{Int}(undef, 3); y1
用这样的办法为向量分配存储空间后可以随后再填入元素值。
可以用fill!()
填入统一的值。
函数名以!
结尾是一个习惯用法,
表示该函数会修改其第一自变量的值。
如:
fill!(y1, 100)
3-element Vector{Int64}:
100
100
100
也可以用fill(value, n)
生成一个元素值都等于value
的长度为n
的一维数组。
可以用collect()
将一个范围转换成可修改的向量。如:
collect(1:5)
5-element Vector{Int64}:
1
2
3
4
5
2.1.4 变量与值
由于Julia的变量仅仅是向实际存储空间的引用(reference), 或称绑定(binding), 所以两个变量可以引用(绑定)到同一个向量的存储空间, 修改了其中一个变量的元素值,则另一个变量的元素也被修改了。 如
= [1,2,3]
x1 = x1
x2 2] = 100
x2[@show x1;
## x1 = [1, 100, 3]
用“===
”或“≡
”(\equiv
+TAB)可以比较两个变量是否同一对象,
如:
=== x1
x2 ## true
允许两个变量指向同一个对象是有用的, 尤其在函数自变量传递时, 但是在一般程序中这种作法容易引起混淆。 向量(或者数组)作为函数自变量时, 调用函数时传递的是引用, 在函数内可以修改传递进来的向量的元素值。
如果需要制作数组的副本,
用copy()
函数。
如
= [1,2,3]
x1 = copy(x1)
x2 2] = -100
x2[@show x1;
## x1 = [1, 2, 3]
=== x1
x2 ## false
将仅有冒号的子集如x[:]
放在等号左边可以修改所有元素,
如果将其放在等号右边并赋值给一个变量,
就可以制作副本,如:
= [1,2,3]
x1 = x1[:]
x2 2] = -100
x2[@show x1;
## x1 = [1, 2, 3]
Julia对象的这种引用或者绑定做法, 初学者比较容易用错。 例如, 下面的程序将一个数组嵌套在另一个数组中:
= [3, 4]
x0 = [1,2, x0]
x1 3][1] = 333
x1[@show x0;
## x0 = [333, 4]
因为x1
中引用(绑定)了x0
的值,
所以x1[3]
和x0
共用同一存储,
修改了x1[3]
就修改了x0
。
那么,
制作x1
的副本能否解决问题?
= [3, 4]
x0 = [1, 2, x0]
x1 = copy(x1)
x2 1] = 111
x2[3][1] = 333
x2[@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()
,
能解决大部分问题:
= [3, 4]
x0 = [1,2, x0]
x1 = deepcopy(x1)
x2 1] = 111
x2[3][1] = 333
x2[@show x2;
## x2 = Any[111, 2, [333, 4]]
@show x1;
## x1 = Any[1, 2, [3, 4]]
@show x0;
## x0 = [3, 4]
仅修改了x2
,没有修改x1
和x0
。
2.1.5 向量的有关函数
为了判断元素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
位置插入指定的一个元素,
原有的元素后移。
如
= [2,3,5]
v3 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!
的区别,
一个是添加一个向量的所有元素到末尾,
一个是添加一个元素到末尾。
如
= [2,3,5]
v3 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!()
函数可以用来在数组中替换元素,如:
= [1, 2, 1, 4, 1]
x replace!(x, 1 => 0)
@show x;
## x = [0, 2, 0, 4, 0]
= [1, 2, 1, 4, 1]
x replace!(x, 1 => 0, 4 => 3)
@show x;
## x = [0, 2, 0, 3, 0]
可以指定一个总替换次数的上限,如:
= [1, 2, 1, 4, 1]
x replace!(x, 1 => 0, 4 => 3, count = 2)
@show x;
## x = [0, 2, 0, 4, 1]
如果要合并两个一维数组并将结果生成一个新数组,
不修改原来的两个数组,
可以用vcat()
函数,如:
= [1,2]; v2 = [-2, -1]
v1 = vcat(v1, v2)
v3 @show v3;
## v3 = [1, 2, -2, -1]
1] = 111
v3[@show v1;
## v1 = [1, 2]
filter!(f, x)
指定一个示性函数f
,将x
中不满足条件的元素删除,
如:
= [2, 3, 5, 7, 11, 13]
x 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
转换成数组。
2.1.6 广播
许多现代的数据分析语言, 如Python, Matlab, R等都存在循环的效率比编译代码低一两个数量级的问题, 在这些语言中, 如果将对向量和矩阵元素的操作向量化, 即以向量和矩阵整体来执行计算, 就可以利用语言内建的向量化计算获得与编译代码相近的执行效率。
Julia语言依靠其LLVM动态编译功能, 对向量和矩阵元素循环时不损失效率, 用显式循环处理向量、矩阵与向量化做法效率相近, 有时显式循环效率更高。 但是,向量化计算的程序代码更简洁。
Julia中的函数, 包括自定义函数, 如果可以对单个标量执行, 将函数名加后缀句点后, 就可以变成向量化版本, 对向量和矩阵执行。 这称为广播。 运算也是如此,运算符前面加点后就可以将标量运算应用到元素之间的运算。 如
sqrt.([1,2,3])
3-element Array{Float64,1}:
1.0
1.4142135623730951
1.7320508075688772
这种向量化对于多个自变量的函数也成立。 通过编译优化, 可以达到专用的以向量作为输入的函数的效率, 如果同一个语句中有多个加点运算, 编译时能够将其尽可能地合并到同一循环中。
2.2 元组(Tuple)
2.2.1 概念和生成
与向量类似的一种数据类型称为元组(tuple)。 如
1, 2, 3)
(## (1, 2, 3)
1, "John", 5.1)
(## (1, "John", 5.1)
元组的元素不要求属于同一类型。
单个元素的元组要有逗号分隔符,如(1,)
是单个元素的元组,
而(1)
不是元组。
元组表面上类似于一维数组, 但是元组属于不可修改(immutable)类型, 不能修改其中的元素。 其存储也与数组不同。
可以用tuple()
函数生成元组。
可以用类似一维数组的方法对元组取子集,
如x[1]
, x[2:3]
等。
如:
= ('a', 'b', 'c', 'd')
x typeof(x)
## NTuple{4,Char}
这个类型的意思是由4个字符组成的元组。
2.2.2 访问片段
1]
x[## 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
2:3]
x[## ('b', 'c')
不允许修改元组中的元素:
2] = 'x' x[
结果出错:
MethodError: no method matching setindex!(::NTuple{4,Char}, ::Char, ::Int64)
.........
2.2.4 赋值等号左边的元组
可以利用元组写法对变量同时赋值, 如
= 13, 17
a, b println("a=", a, " b=", b)
## a=13 b=17
这种赋值可以用来交换两个变量的值,如:
= b, a
a, b println("a=", a, " b=", b)
## a=17 b=13
元组赋值的右侧也可以是数组等其它序列类型,如
= [19, 23]
a, b println("a=", a, " b=", b)
## a=19 b=23
2.3 有名元组
元组可以为元素命名, 这使得其在一定程度上类似于字典。 但是,字典是可变类型(可以修改其中的元素), 元组是不可变类型。 如
= (; name="John", age=32)
tn1 :name]
tn1[## "John"
1]
tn1[## "John"
定义时用了左括号后面加一个分号的格式。 这是推荐的写法, 当有多个元素时可以省略分号, 但有分号使得定义有名元组的意图更明显。
有名元组经常用来给函数的关键字参数赋值, 而这样的关键字参数的值又是类似关键字参数的, 即内容可有可无,可多可少。
要注意有名元组用变量名访问时用的是符号(Symbol), 即不写成字符串的变量名前面有冒号。 也可以用加点格式访问:
tn1.name## "John"
2.4 字典
2.4.1 生成字典
Julia提供了一种Dict数据类型, 是映射的集合, 每个元素是从一个“键”(key)到另一个“值”(value)的映射, 元素之间没有固定次序。如
= Dict("name" => "Li Ming", "age" => 18) d
Dict{String,Any} with 2 entries:
"name" => "Li Ming"
"age" => 18
用Dict()
生成空的字典。
也可以用二元组的数组作为初值定义字典,如
= [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
d2orig = Dict(d2orig) d2
Dict{Char,Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
当键和值分别保存在两个等长的数组中的时候,
可以用zip()
函数将这两个数组合并为二元组的数组,
从而产生字典,如:
= ['a', 'b', 'c', 'd']
x = [1,2,3,4]
y = Dict(zip(x, y)) d2
Dict{Char, Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
用length()
求长度,如:
length(d2)
## 4
字典的键可以用字符串、整数值、浮点数值、元组这样的不可变类型(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
2.4.2 对(Pair)
事实上,
"apple" => 1
这样的写法也是Julia的一种数据类型,
称为“对”(Pair)。
如:
= "apple" => 1
x typeof(x)
## Pair{String, Int64}
用first(x)
取出对的第一项,
用last(x)
取出对的第二项。如:
first(x), last(x))
(## ("apple", 1)
可以用collect(dict)
将字典转换成键、值二元组的一维数组,如:
collect(d2)
4-element Vector{Pair{Char, Int64}}:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
2.4.3 访问元素
访问单个元素如
= Dict("name" => "Li Ming", "age" => 18)
d "age"]
d[## 18
这种功能类似于R语言中用元素名作为下标, 但R中还可以用序号访问元素, 而字典中的元素没有次序,不能用序号访问。
读取字典中单个键的对应值也可以用get(d, key, default)
的格式,
其中default
是元素不存在时的返回值。如:
get(d, "age", 0)
## 18
可以用haskey(d, key)
检查某个键值是否存在,如:
haskey(d, "gender")
## false
给不存在的键值赋值就可以增加一对映射,如
"gender"] = "Male";
d[@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)
合并两个字典,
有共同键时取后一个字典的值。
2.4.4 遍历字典
可以用keys()
函数遍历各个键值,次序不确定:
= Dict('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4)
d2 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)
判断,如:
'a' in keys(d2)
## true
'g' ∈ keys(d2)
## false
在字典中查找某个键是使用散列表(hash table)技术, 所以查找时间不会随元素个数增长而线性增长, 可以比较方便地存储需要快速查找的键值对。
字典存储并没有固定的存储次序。 为了在遍历时按键值的次序, 需要使用如下的效率较低的方法:
for k in sort(collect(keys(d2)))
println(k, " => ", d2[k])
end
a => 1
b => 2
c => 3
d => 4
对字典排序遍历的另一方法是将字典转换成键值对的数组,
然后用sort
排序,
再遍历,如:
= collect(d2)
d2p sort!(d2p, by=first)
for (k, v) in d2p
println(k, " ==> ", v)
end
a ==> 1
b ==> 2
c ==> 3
d ==> 4
可以用values()
遍历各个值,但也没有固定次序。比如
collect(values(d2))
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
可以将字典转换成键值对的列表, 如:
in d2] [(k, v) for (k, v)
4-element Vector{Tuple{Char, Int64}}:
('a', 1)
('c', 3)
('d', 4)
('b', 2)
2.4.5 用生成器生成字典
可以用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
2.4.6 字典应用:频数表
在基本的描述统计中, 经常需要对某个离散取值的变量计算其频数表, 即每个不同值出现的次数。 如果不利用字典类型, 可以先找到所有的不同值, 将每个值与一个序号对应, 然后建立一个一维数组计数, 每个数组元素与一个变量值对应。
利用字典,
我们不需要预先找到所有不同值,而是直接用字典计数,
每个键值是一个不同的变量值,
每个值是一个计数值。
对字典可以用get()
函数提取某个键值对应的值,
并在键值不存在时返回指定的缺省值。
如:
= ["F", "M", "M", "F", "M"]
sex = Dict()
freqs for xi in sex
= get(freqs, xi, 0) + 1
freqs[xi] end
freqs
Dict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
将上述的频数计算功能编写成一个函数如下:
function freqd(x)
= Dict()
y for xi in x
= get(y, xi, 0) + 1
y[xi] end
return y
end
freqd(sex)
Dict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
StatsBase包的countmap
函数实现了上述的freqd
的功能。
如:
using StatsBase
countmap(sex) StatsBase.
Dict{String, Int64} with 2 entries:
"M" => 3
"F" => 2
可以看出StatsBase的版本更为合理, 其返回的字典的数据类型更加精确。
有时返回字典类型不方便使用, 可以返回取值和频数分别的列表:
function freq(x)
= StatsBase.countmap(x)
y return keys(y), values(y)
end
freq(sex)
## (["M", "F"], [3, 2])
= freq("disillusionment")
d3
d3## (['n', 'd', 'i', 's', 'l', 'u', 'o', 'm', 'e', 't'],
## [2, 1, 3, 2, 2, 1, 1, 1, 1, 1])
因为字典的键必须是不可变类型,
所以freq()
中数组x的元素必须是不可变类型。
2.5 集合类型
Julia中Set是集合类型。 集合是可变类型, 没有重复元素, 元素没有次序。
用Set()
生成一个集合,如:
Set(1:3)
Set{Int64} with 3 elements:
2
3
1
Set([1,2,3])
Set{Int64} with 3 elements:
2
3
1
Set(['a', 'b', 'c', 'b'])
Set{Char} with 3 elements:
'a'
'c'
'b'
Set("keep")
Set{Char} with 3 elements:
'k'
'e'
'p'
Set()
输入一个序列(字符串也是序列),
将序列的元素变成集合元素。
注意Set([1,2,3])
正确而Set(1,2,3)
错误。
因为字符串也是序列, 所以, 要生成只有一个字符串的集合, 也需要将其作为字符串的数组输入,如:
Set(["keep"])
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 \backslash B) \cup (B \backslash 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
的不同元素组成的数组。
2.6 自定义复合数据类型
类似于其它编程语言中的struct, class, Julia可以用mutable struct或者struct定义自己的复合数据类型。 例如, 为了表示平面上的一个矩形, 我们需要一个左下角坐标和长度、高度, 就可以定义如下的数据类型:
mutable struct Rectangle
::Real
xll::Real
yll::Real
width::Real
heightend
这定义了一个新的数据类型Rectangle
。
命名的惯例是使用大写字母开头。
其中的xll
, yll
, width
, height
称为这个复合数据结构的“属性”。
生成这个类型的变量:
= Rectangle(0, 0, 2, 1) rect1
这表示左下角坐标为\((0,0)\),宽度为2,高度为1的一个矩形。
用变量名.属性名
的格式访问其中的属性,如:
= Rectangle(0, 0, 2, 1)
rect1
rect1.width## 2
可以针对这样的自定义类型定义相应的运算和函数, 比如, 平移运算的函数:
function move(rect::Rectangle, offset)
Rectangle(rect.xll + offset[1],
+ offset[2],
rect.yll
rect.width, rect.height)end
测试:
= move(rect1, (20, 10))
rect2 ## Rectangle(20, 10, 2, 1)
用struct
定义的复合数据,
其中的属性不允许修改。
用mutable struct
定义的复合数据,
其中的属性是可以修改的。