## 3.3 原生数据结构

Julia 有多种原生数据结构。 它们都是某种结构化数据形式的抽象。 本书将讨论最常用的数据结构。 它们都能够保存同类型或异构的数据。 因为它们都是集合， 所以都能通过 for 循环进行 遍历 。 接下来的讨论包括 StringTupleNamedTupleUnitRangeArraysPairDict, Symbol

first(methodswith(String), 5)
[1] write(fp::FilePathsBase.SystemPath, x::Union{String, Vector{UInt8}}) in FilePathsBase at /home/runner/.julia/packages/FilePathsBase/dVWLK/src/system.jl:380
[2] write(fp::FilePathsBase.SystemPath, x::Union{String, Vector{UInt8}}, mode) in FilePathsBase at /home/runner/.julia/packages/FilePathsBase/dVWLK/src/system.jl:380
[3] write(iod::HTTP.DebugRequest.IODebug, x::String) in HTTP.DebugRequest at /home/runner/.julia/packages/HTTP/aTjcj/src/IODebug.jl:38
[4] write(buffer::FilePathsBase.FileBuffer, x::String) in FilePathsBase at /home/runner/.julia/packages/FilePathsBase/dVWLK/src/buffer.jl:85
[5] write(io::IO, s::Union{SubString{String}, String}) in Base at strings/io.jl:244

### 3.3.1 对运算符和函数进行广播

[1, 2, 3] .+ 1
[2, 3, 4]

logarithm.([1, 2, 3])
[0.0, 0.6931471805599569, 1.0986122886681282]

### 3.3.2 带感叹号 ! 的函数

function add_one!(V)
for i in 1:length(V)
V[i] += 1
end
return nothing
end
my_data = [1, 2, 3]

my_data
[2, 3, 4]

### 3.3.3 字符串

Julia 中使用双引号分隔符表示 字符串 :

typeof("This is a string")
String

text = "
This is a big multiline string.
As you can see.
It is still a String to Julia.
"

This is a big multiline string.
As you can see.
It is still a String to Julia.


s = """
This is a big multiline string with a nested "quotation".
As you can see.
It is still a String to Julia.
"""
This is a big multiline string with a nested "quotation".
As you can see.
It is still a String to Julia.


#### 3.3.3.1 字符串连接

hello = "Hello"
goodbye = "Goodbye"

hello * goodbye
HelloGoodbye

join([hello, goodbye], " ")
Hello Goodbye

#### 3.3.3.2 字符串插值

end
end

test_interpolated(3.14, 3.14)
3.14 is equal to 3.14

#### 3.3.3.3 字符串处理

Julia 中有多个函数处理字符串。 接下来将讨论那些最常用的函数。 另外注意，这些函数大多数都支持 正则表达式 (RegEx) 作为参数。 本书不包含 RegEx，但可以自主学习，尤其是如果你的大多数工作都需要处理文本数据。

julia_string = "Julia is an amazing open source programming language"
Julia is an amazing open source programming language
1. containsstartswithendswith： 条件函数 （返回 truefalse） 如果第二个参数是：

• 第一个参数的 子串

contains(julia_string, "Julia")
true
• 第一个参数的 前缀

startswith(julia_string, "Julia")
true
• 第一个参数的 后缀

endswith(julia_string, "Julia")
false
2. lowercaseuppercasetitlecaselowercasefirst

lowercase(julia_string)
julia is an amazing open source programming language
uppercase(julia_string)
JULIA IS AN AMAZING OPEN SOURCE PROGRAMMING LANGUAGE
titlecase(julia_string)
Julia Is An Amazing Open Source Programming Language
lowercasefirst(julia_string)
julia is an amazing open source programming language
3. replace：介绍一种称为 Pair 的新语法：

replace(julia_string, "amazing" => "awesome")
Julia is an awesome open source programming language
4. split：使用分隔符分隔字符串：

split(julia_string, " ")
SubString{String}["Julia", "is", "an", "amazing", "open", "source", "programming", "language"]

#### 3.3.3.4 字符串转换

my_number = 123
typeof(string(my_number))
String

typeof(parse(Int64, "123"))
Int64

tryparse(Int64, "A very non-numeric string")
nothing

### 3.3.4 元组（Tuple）

Julia 中有一类名为 元组特殊数据类型。 它们经常用在函数中，而函数又是 Julia 的重要组成部分，因此每一个 Julia 用户都应该了解元组的基础。

my_tuple = (1, 3.14, "Julia")
(1, 3.14, "Julia")

my_tuple[2]
3.14

return_multiple = add_multiply(1, 2)
typeof(return_multiple)
Tuple{Int64, Int64}

1, 2
(1, 2)

map((x, y) -> x^y, 2, 3)
8

map((x, y, z) -> x^y + z, 2, 3, 1)
9

### 3.3.5 命名元组

my_namedtuple = (i=1, f=3.14, s="Julia")
(i = 1, f = 3.14, s = "Julia")

my_namedtuple.s
Julia

i = 1
f = 3.14
s = "Julia"

my_quick_namedtuple = (; i, f, s)
(i = 1, f = 3.14, s = "Julia")

### 3.3.6 Ranges

Julia 中的 range 表示一段开始和结束边界之间的序列。 语法是 start:stop

1:10
1:10

typeof(1:10)
UnitRange{Int64}

[x for x in 1:10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

typeof(1.0:10.0)
StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}

0.0:0.2:1.0
0.0:0.2:1.0

collect(1:10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

### 3.3.7 数组

myarray = [1, 2, 3]
[1, 2, 3]

myarray = ["text", 1, :symbol]
Any["text", 1, :symbol]

#### 3.3.7.1 数组类型

• Vector{T}: 一维 数组。 Array{T, 1} 的别名。
• Matrix{T}: 二维 数组。 Array{T, 2} 的别名。

#### 3.3.7.2 数组构造

my_vector = Vector{Float64}(undef, 10)
[0.0, 6.9077153247747e-310, 6.90771829209107e-310, 0.0, 6.9077153247747e-310, 6.9077153027599e-310, 0.0, 6.9077153247747e-310, 6.90771661543227e-310, 0.0]

my_matrix = Matrix{Float64}(undef, 10, 2)
10×2 Matrix{Float64}:
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310
6.90773e-310  6.90773e-310

• zeros 将所有元素初始化为 0。 注意默认类型为 Float64，如果需要可以更改类型：

my_vector_zeros = zeros(10)
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
my_matrix_zeros = zeros(Int64, 10, 2)
10×2 Matrix{Int64}:
0  0
0  0
0  0
0  0
0  0
0  0
0  0
0  0
0  0
0  0
• ones 将所有元素初始化为 1。

my_vector_ones = ones(Int64, 10)
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
my_matrix_ones = ones(10, 2)
10×2 Matrix{Float64}:
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0
1.0  1.0

my_matrix_π = Matrix{Float64}(undef, 2, 2)
fill!(my_matrix_π, 3.14)
2×2 Matrix{Float64}:
3.14  3.14
3.14  3.14

[[1 2]
[3 4]]
2×2 Matrix{Int64}:
1  2
3  4

Float64[[1 2]
[3 4]]
2×2 Matrix{Float64}:
1.0  2.0
3.0  4.0

Bool[0, 1, 0, 1]
Bool[0, 1, 0, 1]

[ones(Int, 2, 2) zeros(Int, 2, 2)]
2×4 Matrix{Int64}:
1  1  0  0
1  1  0  0
[zeros(Int, 2, 2)
ones(Int, 2, 2)]
4×2 Matrix{Int64}:
0  0
0  0
1  1
1  1
[ones(Int, 2, 2) [1; 2]
[3 4]            5]
3×3 Matrix{Int64}:
1  1  1
1  1  2
3  4  5

[x^2 for x in 1:10]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

[x*y for x in 1:10 for y in 1:2]
[1, 2, 2, 4, 3, 6, 4, 8, 5, 10, 6, 12, 7, 14, 8, 16, 9, 18, 10, 20]

[x^2 for x in 1:10 if isodd(x)]
[1, 9, 25, 49, 81]

Float64[x^2 for x in 1:10 if isodd(x)]
[1.0, 9.0, 25.0, 49.0, 81.0]

"aa" * "bb"

aabb

• cat：沿着指定的 dims 串联输入的数组

cat(ones(2), zeros(2), dims=1)
[1.0, 1.0, 0.0, 0.0]
cat(ones(2), zeros(2), dims=2)
2×2 Matrix{Float64}:
1.0  0.0
1.0  0.0
• vcat: 垂直串联， cat(...; dims=1) 的缩写

vcat(ones(2), zeros(2))
[1.0, 1.0, 0.0, 0.0]
• hcat: 水平串联， cat(...; dims=2) 的缩写

hcat(ones(2), zeros(2))
2×2 Matrix{Float64}:
1.0  0.0
1.0  0.0

#### 3.3.7.3 数组检测

eltype(my_matrix_π)
Float64

• length: 元素的总数

length(my_matrix_π)
4
• ndims: 维度的个数

ndims(my_matrix_π)
2
• size: 此例有一些复杂。 默认情况下将返回包含所有数组维度的元组。

size(my_matrix_π)
(2, 2)

你可以在size的第二个参数指定想要的维度。 如下，第二个轴为列：

size(my_matrix_π, 2)
2

#### 3.3.7.4 数组索引和切片

my_example_vector = [1, 2, 3, 4, 5]

my_example_matrix = [[1 2 3]
[4 5 6]
[7 8 9]]

my_example_vector[2]
2

my_example_matrix[2, 1]
4

Julia 也为数组的 第一个最后一个 元素定义了特殊的关键字： beginend。 例如，可以如下方式检索向量的倒数第二个元素：

my_example_vector[end-1]
4

my_example_matrix[end, begin+1]
8

my_example_vector[2:4]
[2, 3, 4]

my_example_matrix[2, :]
[4, 5, 6]

my_example_matrix[begin+1:end, end]
[6, 9]

#### 3.3.7.5 数组操作

my_example_matrix[2, 2] = 42
my_example_matrix
3×3 Matrix{Int64}:
1   2  3
4  42  6
7   8  9

my_example_matrix[3, :] = [17, 16, 15]
my_example_matrix
3×3 Matrix{Int64}:
1   2   3
4  42   6
17  16  15

typeof(my_example_matrix[3, :])
Vector{Int64} (alias for Array{Int64, 1})

six_vector = [1, 2, 3, 4, 5, 6]
three_two_matrix = reshape(six_vector, (3, 2))
three_two_matrix
3×2 Matrix{Int64}:
1  4
2  5
3  6

reshape(three_two_matrix, (6, ))
[1, 2, 3, 4, 5, 6]

logarithm.(my_example_matrix)
3×3 Matrix{Float64}:
0.0      0.693147  1.09861
1.38629  3.73767   1.79176
2.83321  2.77259   2.70805

Julia中的点运算符非常通用。 可以使用它广播中缀运算符：

my_example_matrix .+ 100
3×3 Matrix{Int64}:
101  102  103
104  142  106
117  116  115

map(logarithm, my_example_matrix)
3×3 Matrix{Float64}:
0.0      0.693147  1.09861
1.38629  3.73767   1.79176
2.83321  2.77259   2.70805

map(x -> 3x, my_example_matrix)
3×3 Matrix{Int64}:
3    6   9
12  126  18
51   48  45

(x -> 3x).(my_example_matrix)
3×3 Matrix{Int64}:
3    6   9
12  126  18
51   48  45

map(x -> x + 100, my_example_matrix[:, 3])
[103, 106, 115]

# rows
mapslices(sum, my_example_matrix; dims=1)
1×3 Matrix{Int64}:
22  60  24
# columns
mapslices(sum, my_example_matrix; dims=2)
3×1 Matrix{Int64}:
6
52
48

#### 3.3.7.6 数组迭代

simple_vector = [1, 2, 3]

empty_vector = Int64[]

for i in simple_vector
push!(empty_vector, i + 1)
end

empty_vector
[2, 3, 4]

forty_twos = [42, 42, 42]

empty_vector = Int64[]

for i in eachindex(forty_twos)
push!(empty_vector, i)
end

empty_vector
[1, 2, 3]

column_major = [[1 3]
[2 4]]

row_major = [[1 2]
[3 4]]

indexes = Int64[]

for i in column_major
push!(indexes, i)
end

indexes
[1, 2, 3, 4]

indexes = Int64[]

for i in row_major
push!(indexes, i)
end

indexes
[1, 3, 2, 4]

• eachcol: 先沿着列方向迭代

first(eachcol(column_major))
[1, 2]
• eachrow: 先沿着行方向迭代

first(eachrow(column_major))
[1, 3]

### 3.3.8 Pair

my_pair = "Julia" => 42
"Julia" => 42

my_pair.first
Julia
my_pair.second
42

first(my_pair)
Julia
last(my_pair)
42

Pair 广泛应用于数组操作和数据可视化。本书的 DataFrames.jl (Section 4) 和 Makie.jl (Section 5) 章节将会在主要程序函数中用到由各种对象构成的 Pair。 例如，在 DataFrames.jl 这一章，可以看到 :a => :b 的用途是将 :a 重命名为 :b

### 3.3.9 字典

Julia 中有两种构造 Dict 的方法。 第一种是向 Dict 构造器传递由 (key, value) 元组构成的向量：

name2number_map = Dict([("one", 1), ("two", 2)])
Dict{String, Int64} with 2 entries:
"two" => 2
"one" => 1

name2number_map = Dict("one" => 1, "two" => 2)
Dict{String, Int64} with 2 entries:
"two" => 2
"one" => 1

name2number_map["one"]
1

name2number_map["three"] = 3
3

"two" in keys(name2number_map)
true

delete!(name2number_map, "three")
Dict{String, Int64} with 2 entries:
"two" => 2
"one" => 1

popped_value = pop!(name2number_map, "two")
2

name2number_map
Dict{String, Int64} with 1 entry:
"one" => 1

DataFrames.jl (Section 4) 中的数据操作和 Makie.jl (Section 5) 中的数据可视化也用到了很多 Dict。 因此，了解它们的基本功能十分重要。

A = ["one", "two", "three"]
B = [1, 2, 3]

name2number_map = Dict(zip(A, B))
Dict{String, Int64} with 3 entries:
"two" => 2
"one" => 1
"three" => 3

name2number_map["three"]
3

### 3.3.10 Symbol

Symbol 实际上 并不是 一种数据结构。 它是一种类型，并且其行为类似于字符串。 与引号包围文本的字符串不同，Symbol 以冒号 (:) 开始并且可以包含下划线：

sym = :some_text
:some_text

s = string(sym)
some_text
sym = Symbol(s)
:some_text

### 3.3.11 Splat 运算符

Julia 中有一种 splatting 运算符 ...，它被用于在函数调用时转换 参数序列。 在 数据操作数据可视化 章节中，我们偶尔会在调用某些函数时使用 splatting

add_elements(a, b, c) = a + b + c
add_elements (generic function with 1 method)

my_collection = [1, 2, 3]

add_elements(my_collection[1], my_collection[2], my_collection[3])
6

add_elements(my_collection...)
6

add_elements(my_collection...) == add_elements(my_collection[1], my_collection[2], my_collection[3])
true

add_elements(1:3...)
6

1. 13. 或者说，指向每一列元素的内存地址指针相邻存储。↩︎

2. 14. 更简单的原因是 firstlast 也适用于其他集合，所以需要记住的就更少。↩︎

CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso, 刘贵欣 (中文翻译), 田俊 （中文审校）