B Julia语言入门(*)

B.1 Julia的安装和运行

B.1.1 Julia程序语言介绍

Julia程序语言是一种计算机编程语言, 就像C、C++、Fortran、Java、R、Python、Matlab等程序语言一样。 Julia语言历史比较短,发布于2012年,是MIT的几位作者和全世界的参与者共同制作的。 主网站在https://julialang.org/

Julia与R、Python等一样是动态类型语言, 程序与R、Python一样简单, 但是它先进的设计使得Julia程序的效率基本达到和C、Fortran等强类型语言同样的高效率。 尤其适用于数值计算,在现今的大数据应用中也是特别合适的语言, 排在Python、R之后,已经获得广泛的关注, 现在用户较少只是因为历史还太短。

B.1.2 Julia软件安装和运行

从Julia网站下载安装Julia的命令行程序,称为REPL界面。

运行方式是在一个字符型窗口中, 在提示行julia>后面键入命令, 回车后在下面显示结果。

在MS Windows操作系统中, 设存放Julia源程序和数据文件的目录为c:\work, 将安装后显示在桌面上的Julia图标复制到目录中, 从右键选“属性”,将“起始位置”栏改为空白。 这样读写数据和程序文件的默认位置就是图标所在的目录。

Julia源程序用.jl作为扩展名, 为了运行当前目录中的“myprog.jl”文件, 在命令行用命令

julia> include("myprog.jl")

(其中julia>是自动显示的提示)。

B.1.3 Julia的其它运行方式

  1. 安装REPL界面后, 下载安装微软的软件集成开发界面Visual Studio Code, 然后安装Julia语言支持模块。

  2. 安装Anaconda软件,并安装Julia的IJulia模块, 将IJulia所需要的Python和Jupyter可执行程序的路径指定为安装的Anaconda软件的位置。 这可以同时获得Python语言和一些科学计算、数据整理、统计建模、机器学习等软件。

  3. 在REPL界面,安装Pluto笔记本软件,方法如:

    using Pkg; Pkg.add("Pluto")

    安装好以后,只要在REPL中运行

    using Pluto; Pluto.run()

    就可以进入一个笔记本形式的软件开发环境。

B.1.4 附加软件包安装和调用

Julia语言安装好以后, 已经默认安装了许多标准的软件包(library), 称为标准库, 比如线性代数计算的LinearAlgebra标准库。 其中的Base库是不需要额外声明就可以使用的, 其它的库则需要用using 库名声明, 然后才可以访问其中的功能。

为了安装某个额外的软件包(库), 方法如:

using Pkg; Pkg.add("库名")

参见:

B.2 Julia的基本数据和相应运算

B.2.1 整数与浮点数

Julia程序中的整数值可以直接写成如123或者-123这样。 虽然整数有多种类型, 一般程序中不必特别关心整数常量的具体类型。 Julia允许使用特别长的整数,这时其类型为BigInt。

Julia的浮点数可以写成带点的形式如123.0, 1.23, 也可以写成带有10的幂次如1.23e3(表示\(1.23\times 10^3\)), 1.23e-3(表示\(1.23\times 10^{-3}\))。 这些写法都属于Float64类型的浮点数。 Julia还有其他类型的浮点数,但是科学计算中主要使用Float64类型, 在别的语言中这称为双精度浮点数。

B.2.2 四则运算

表示加、减、乘、除、乘方的运算符分别为:

+  -   *  /  ^

浮点数的四则运算遵循传统的算数运算规则和优先级规定。如

1.3 + 2.5*2.0 - 3.6/1.2 + 1.2^2
4.74

表示 \[1.3 + 2.5 \times 2.0 - 3.6 \div 1.2 + 1.2^2\]

B.2.3 整数的四则运算

整数加法、减法、乘法结果仍为整数, 这样因为整数的表示范围有限,有可能发生溢出。 如

10 + 2*3 - 3*4
4

整数用“/”作的除法总是返回浮点数,即使结果是整数也是一样:

10/2
5.0

整数用a % b表示a整除b的余数,结果符号总是取a的符号。如

10 % 3
1

整数与浮点数的混合运算会将整数转换成浮点数再计算。

B.2.4 字符串

单个字符在两边用单撇号界定,如'A''囧'。 字符都是用Unicode编码存储,具体使用UTF-8编码。

零到多个字符组成字符串, 程序中的字符串在两边用双撇号界定,如 "A cat""泰囧"

对于占据多行的字符串, 可以在两侧分别用三个双撇号界定。如

"""
这是第一行
这是第二行
三个双撇号界定的字符串中间的单个双撇号"不需要转义
"""
"这是第一行\n这是第二行\n三个双撇号界定的字符串中间的单个双撇号\"不需要转义\n"

B.2.5 字符串连接

用星号“*”连接两个字符串,如

"过去的" * "历史" * "不能忘记"
"过去的历史不能忘记"

B.3 变量

B.3.1 变量

变量名是一个标识符, 用来指向某个值在计算机内存中的存储位置。 变量名可以用英文大小写字母、下划线、数字、允许的Unicode字符。 变量名不允许使用空格、句点以及其它标点符号和井号之类的特殊字符。

给变量赋值,即将变量名与一个内存中的内容联系起来,也称为绑定(binding), 使用等号“=”,等号左边写变量名,右边写要保存到变量中的值。如

x = 123
123
y = 1+3/2
2.5
addr100871 = "北京市海淀区颐和园路5号"
"北京市海淀区颐和园路5号"

变量的类型是由它保存的(指向的内存中的)值的类型决定的, 不需要说明变量类型(Julia允许说明变量类型,但一般不需要)。

变量赋值后,就可以参与运算,如:

x = 123
y = 1+3/2
x + y*2
128.0

B.3.2 简单的输出

在Julia命令行,键入变量名或者计算表达式直接在下面显示结果。 可以用println()函数显示指定的变量和结果。如

println(x + y*2)
128.0
println("x=", x, " y=", y, " x + y*2 =", x+y*2)
x=123 y=2.5 x + y*2 =128.0

B.4 向量

B.4.1 向量

在程序中直接定义一个向量,用方括号内写多个逗号分隔的数值,如

v1 = [1, 3, 4, 9, 13]
5-element Array{Int64,1}:
  1
  3
  4
  9
 13
v2 = [1.5, 3, 4, 9.12]
4-element Array{Float64,1}:
 1.5 
 3.0 
 4.0 
 9.12

其中v1是整数型的向量, v2是浮点型Float64的向量。

向量实际上是一维数组。

可以用1:5定义一个范围, 在仅使用其中的元素值而不改写时作用与[1, 2, 3, 4, 5]类似。 1:2:9定义带有跨度的范围, 与[1, 3, 5, 7, 9]类似。

1:5
1:5
1:2:9
1:2:9

B.4.2 向量下标

x是向量,i是正整数,x[i]表示向量的第i个元素。如

v1[2]
3

对元素赋值将在原地修改元素的值,如

v1[2] = -999; v1
5-element Array{Int64,1}:
    1
 -999
    4
    9
   13

B.4.3 用范围作为下标

下标可以是一个范围,如

v1[2:4]
3-element Array{Int64,1}:
 -999
    4
    9

熟悉Python语言的读者要注意,这里下标范围的终点是包含在内的, 而Python用这种方法取子集时不包括范围的终点。

end表示最后一个下标,如

v1[4:end]
2-element Array{Int64,1}:
  9
 13
v1[1:(end-1)]
4-element Array{Int64,1}:
    1
 -999
    4
    9

B.4.4 数组作为下标

向量的下标也可以是一个下标数组,如

v1[[1, 3, 5]]
3-element Array{Int64,1}:
  1
  4
 13

取出的多个元素可以修改,可以赋值为同一个标量,如:

v1[1:3] = 0; v1
5-element Array{Int64,1}:
  0
  0
  0
  9
 13

也可以分别赋值,如

v1[[1, 3, 5]] = [101, 303, 505]; v1
5-element Array{Int64,1}:
 101
   0
 303
   9
 505

B.4.5 向量与标量的运算

向量与一个标量作四则运算, 将运算符前面加句点“.”:

.+   .-   .*   ./   .^

表示向量的每个元素分别与该标量作四则运算, 结果仍是向量。如

v1 = [1, 3, 4, 9, 13]
v1 .+ 100
5-element Array{Int64,1}:
 101
 103
 104
 109
 113
100 .- v1
5-element Array{Int64,1}:
 99
 97
 96
 91
 87
v1 .* 2
5-element Array{Int64,1}:
  2
  6
  8
 18
 26
v1 ./ 10
5-element Array{Float64,1}:
 0.1
 0.3
 0.4
 0.9
 1.3
v1 .^ 2
5-element Array{Int64,1}:
   1
   9
  16
  81
 169

B.4.6 向量与向量的四则运算

两个等长的向量之间作加点的四则运算,表示对应元素作相应的运算。如

v1 = [1, 3, 4, 9, 13]
v3 = [2, 5, 6, 7, 10]
v1 .+ v3
5-element Array{Int64,1}:
  3
  8
 10
 16
 23
v1 .- v3
5-element Array{Int64,1}:
 -1
 -2
 -2
  2
  3
v1 .* v3
5-element Array{Int64,1}:
   2
  15
  24
  63
 130
v1 ./ v3
5-element Array{Float64,1}:
 0.5     
 0.6     
 0.666667
 1.28571 
 1.3     

B.4.7 向量初始化

zeros(n)可以生成元素类型为Float64、元素值为0、长度为n的向量,如

zeros(5)
5-element Array{Float64,1}:
 0.0
 0.0
 0.0
 0.0
 0.0

Vector{Float64}(undef, n)可以生成元素类型为Float64的长度为n的向量, 元素值未初始化,如

Vector{Float64}(undef, 3)
3-element Array{Float64,1}:
 1.114676523e-315
 1.92399178e-315
 1.5759804e-315

类似可以生成其它元素类型的向量,如

Vector{Int}(undef, 3)
3-element Array{Int64,1}:
 378739344
 378739376
 155525920

用这样的办法为向量分配存储空间后可以随后再填入元素值。

B.4.8 向量复制

定义一个向量后, 此向量的变量名就“绑定”在某个内存地址上。 修改变量元素实际是修改了变量名所绑定的地址中某个位置的值。如

v = [2, 3, 5, 7, 11]
v[3] = 0
v
5-element Array{Int64,1}:
  2
  3
  0
  7
 11

将向量赋给另外一个向量,并不能实现复制; 两个向量实际上是绑定到了相同的内存地址。如

w = v
w
5-element Array{Int64,1}:
  2
  3
  0
  7
 11
v[1] = 97
w
5-element Array{Int64,1}:
 97
  3
  0
  7
 11

可以看出修改了v的元素值,w的元素值也被修改了, 因为这两个变量名指向的是相同的内存地址。 为了制作一个向量的副本, 不能使用直接赋值的方法,而是使用copy()函数。如

v = [2, 3, 5, 7, 11]
w = copy(v)
v[3] = 0
w
5-element Array{Int64,1}:
  2
  3
  5
  7
 11

v被修改后,其副本w未受影响。 另外,给w另外赋值,相当于解除了w原来的绑定, 并将w绑定到了将v的内容复制到另一内存地址的副本地址上。

B.4.9 向量的循环遍历

可以用for循环对向量的每个元素遍历访问。格式如

for i in eachindex(v1)
  println("v1[", i, "] = ", v1[i])
end
v1[1] = 1
v1[2] = 3
v1[3] = 4
v1[4] = 9
v1[5] = 13

这里i是循环产生的向量下标。

B.4.10 向量的输出

v2 = [1.5, 3, 4, 9.12], 为了将其按显示格式保存到文件“tmp1.txt”中,可用如下代码:

using DelimitedFiles
writedlm("tmp1.txt", v2, ' ')

结果文件中每个数占一行。

B.4.11 向量的输入

假设文件“vecstore.txt”中包含如下的内容:

1.2 -5  3.6
7.8 9.12 4.11

可以用如下代码将文件中的数据读入到一个向量v4中:

using DelimitedFiles
v4 = readdlm("vecstore.txt")[:]; v4

B.5 矩阵

B.5.1 矩阵

前面讲的向量是一维数组,不区分行向量还是列向量。

矩阵是二维数组,有两个下标:行下标和列下标。

在方括号内两个同行的元素之间用空格分隔, 两行之间用分号分隔,可以在程序中输入矩阵,如

A1 = [1 2 3; 4 5 6]
2×3 Array{Int64,2}:
 1  2  3
 4  5  6

也可以直接用换行符分隔不同行,例如

A1 = [1 2 3
      4 5 6]

B.5.2 矩阵下标

A是矩阵,则A[i,j]表示A的第i行第j列元素,如:

A1[2,3]
6

给元素赋值可以在矩阵中修改元素值,如:

A1[2,3] = -6; A1
2×3 Array{Int64,2}:
 1  2   3
 4  5  -6

B.5.3 矩阵列和行

A是矩阵, 则A[:, j]表示A的第j列元素组成的向量(一维数组),如

A1[:, 2]
2-element Array{Int64,1}:
 2
 5

A[i, :]表示A的第i行元素组成的向量(一维数组),如

A1[2, :]
3-element Array{Int64,1}:
  4
  5
 -6

取出后的列或者行不分行向量和列向量。

B.5.4 子矩阵

如果A是矩阵,IJ是范围或者向量, 则A[I,J]表示A的行号在I中的行与列号在J中的列交叉所得的子矩阵,如

A1[1:2, 2:3]
2×2 Array{Int64,2}:
 2   3
 5  -6

用冒号“:”作为行下标或列下标表示取该维的全部下标。

B.5.5 子矩阵赋值

可以给子矩阵赋值,赋值为一个标量使得对应元素都改为该标量值;如

A1[1:2, 2:3] = 0; A1
2×3 Array{Int64,2}:
 1  0  0
 4  0  0

给子矩阵赋值为一个同样大小的子矩阵给对应元素赋值,如

A1[1:2, 2:3] = [102 103; 202 203]; A1
2×3 Array{Int64,2}:
 1  102  103
 4  202  203

B.5.6 矩阵初始化

zeros(m, n)可以生成元素类型为Float64、元素值为0的\(m\times n\)矩阵,如

zeros(2, 3)
2×3 Array{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0

Array{Float64}(undef, m, n)可以生成元素类型为Float64的\(m \times n\)矩阵, 元素值未初始化,如

Array{Float64}(undef, 2, 3)
2×3 Array{Float64,2}:
 3.90752e-316  6.78756e-316  5.18128e-316
 6.61297e-316  6.81373e-316  3.87102e-316

类似可以生成其它元素类型的矩阵,如

Array{Int}(undef, 2, 3)
2×3 Array{Int64,2}:
 386423696  78323216  78323216
  79156592  78323216  78331944

用这样的办法先为矩阵分配存储空间,然后可以再填入元素值。

B.5.7 矩阵元素遍历

对行下标和列下标分别循环,行下标变化最快,如

A1 = [1 2 3; 4 5 6]
2×3 Array{Int64,2}:
 1  2  3
 4  5  6
for j = 1:size(A1,2), i = 1:size(A1,1)
  println("A1[", i, ", ", j, "] = ", A1[i, j])
end
A1[1, 1] = 1
A1[2, 1] = 4
A1[1, 2] = 2
A1[2, 2] = 5
A1[1, 3] = 3
A1[2, 3] = 6

另一种办法是用类似于向量遍历的方法,如:

for i in eachindex(A1)
  println("A1[", i, "] = ", A1[i])
end
A1[1] = 1
A1[2] = 4
A1[3] = 2
A1[4] = 5
A1[5] = 3
A1[6] = 6

B.5.8 矩阵读入

设当前目录中文件“vecstore.txt”中包含如下内容:

1.2 -5  3.6
7.8 9.12 4.11

将文件中的内容每一行看作矩阵的一行, 文件中保存了一个\(2 \times 3\)矩阵。 读入方法如下:

using DelimitedFiles
Ain = readdlm("vecstore.txt"); Ain

2×3 Array{Float64,2}:
 1.2  -5.0   3.6
 7.8   9.12  4.11

B.5.9 保存矩阵到文件中

考虑上面的Ain矩阵,为了将其按文本文件格式保存到“tmp2.txt”中, 用如下程序:

using DelimitedFiles
writedlm("tmp2.txt", Ain, ' ')

B.5.10 矩阵与标量的四则运算

矩阵与一个标量之间用加点的四则运算符号进行运算, 与向量和标量之间的运算类似, 表示矩阵的每个元素和该变量的四则运算, 结果仍为矩阵。如

A1 = [1 2 3; 4 5 6]
2×3 Array{Int64,2}:
 1  2  3
 4  5  6
A1 .+ 100
2×3 Array{Int64,2}:
 101  102  103
 104  105  106
100 .- A1
2×3 Array{Int64,2}:
 99  98  97
 96  95  94
A1 .* 2
2×3 Array{Int64,2}:
 2   4   6
 8  10  12
A1 ./ 10
2×3 Array{Float64,2}:
 0.1  0.2  0.3
 0.4  0.5  0.6
A1 .^ 2
2×3 Array{Int64,2}:
  1   4   9
 16  25  36

B.5.11 两个矩阵之间的四则运算

两个同样大小的矩阵之间用加点的四则运算符号进行运算, 表示两个矩阵的对应元素的运算。如

A2 = A1 .* 100
2×3 Array{Int64,2}:
 100  200  300
 400  500  600
A1 .+ A2
2×3 Array{Int64,2}:
 101  202  303
 404  505  606
A2 .- A1
2×3 Array{Int64,2}:
  99  198  297
 396  495  594
A1 .* A2
2×3 Array{Int64,2}:
  100   400   900
 1600  2500  3600
A2 ./ A1
2×3 Array{Float64,2}:
 100.0  100.0  100.0
 100.0  100.0  100.0

B.5.12 矩阵乘法

A * B表示矩阵乘法。 如

A3 = [11 12; 21 22]
2×2 Array{Int64,2}:
 11  12
 21  22
A3 * A1
2×3 Array{Int64,2}:
  59   82  105
 109  152  195

一个矩阵与一个向量(一维数组)作矩阵乘法, 向量自动变成列向量,如:

A3 * [1, -1]
2-element Array{Int64,1}:
 -1
 -1

注意结果是向量(一维数组),而不是\(2 \times 1\)矩阵(二维数组)。

行向量可以直接表示成方括号内多个数值之间用空格分隔的格式,如

[1,  -1] * [1  -1]
2×2 Array{Int64,2}:
  1  -1
 -1   1

又如

[1  -1] * A3
1×2 Array{Int64,2}:
 -10  -10

注意结果是\(1 \times 2\)矩阵,即行向量,而不是向量。 向量是一维数组,行向量是二维数组。

从以上例子可以看出在矩阵运算中向量可以看成是列向量, 矩阵乘法结果如果是列向量,也会表示成向量(一维数组)。

B.5.13 矩阵转置

A.’表示矩阵A的转置; 用A’表示矩阵A的共轭转置。 如

A1
2×3 Array{Int64,2}:
 1  2  3
 4  5  6
A1.'
3×2 Array{Int64,2}:
 1  4
 2  5
 3  6

两个向量x和y的内积用dot(x, y)表示, 而不要写成x.’ * y。如

dot([1, -1], [2, 3])

B.5.14 矩阵求逆和解线性方程组

inv(A)表示\(A^{-1}\)。如

A4 = [1 3; 3 1]
2×2 Array{Int64,2}:
 1  3
 3  1
inv(A4)
2×2 Array{Float64,2}:
 -0.125   0.375
  0.375  -0.125

A \ B表示\(A^{-1} B\), 当B是向量或者列向量时, 就是求解线性方程组\(A x = B\)中的\(x\)。 如

A4 \ [-2, 2]
2-element Array{Float64,1}:
  1.0
 -1.0

B.6 自定义函数

B.6.1 介绍

Julia语言支持自定义函数。 在计算机程序语言中, 函数是代码模块化、代码复用的基础。 将常用的计算或者操作写成函数以后, 每次要进行这样的计算或者操作, 只要简单地调用函数即可。 将复杂的任务分解成一个个函数, 可以使得任务流程更加明晰, 任务的不同阶段之间减少互相干扰。 另外, 用自定义函数表示的算法才能充分利用Julia的即时编译优势。

B.6.2 自定义函数的单行格式

类似于\(f(x) = x^2 + 3 x + 1\)这样的简单函数, 可以用一行代码写成

f(x) = x^2 + 3*x + 1
f (generic function with 1 method)

调用如

f(2)
11
f(1.1)
5.510000000000001

B.6.3 自定义函数的多行格式

需要用多行才能完成的计算或者操作, 就需要写成多行的形式。格式如

function funcname(x, y, z)
  ...
end

其中funcname是函数名称, x, y, z等是自变量名, ...是函数内的语句或表达式, 函数以最后一个表达式为返回值(结果)。

例如,写一个函数,以向量的形式输入一个变量的样本值, 计算样本标准差: \[ s = \sqrt{ \frac{1}{n-1} \sum_{i=1}^n (x_i - \bar x)^2 } \] 自定义函数如

function mysd(x)
  n = length(x)
  mx = 0.0
  for z in x
    mx += z
  end
  mx /= n
  s = 0.0
  for z in x
    s += (z - mx)^2
  end
  sqrt(s / (n-1))
end
mysd (generic function with 1 method)

调用如

mysd([1, 2, 3, 4, 5])
1.5811388300841898

事实上,上面的函数定义可以用已有的一些函数进行简化。 比如,sum(x)对向量x的元素求和,于是以上的函数可以写成:

function mysd_simple(x)
  n = length(x)
  mx = sum(x)/n
  sqrt( sum(x .- mx) / (n-1) )
end
mysd_simple (generic function with 1 method)

第二个版本只是利用了向量化和已有函数, 在Julia中其运行效率并不比第一个版本更好, 甚至于不如第一个版本。 Julia语言与R、Matlab等语言不同, 显式的循环一般有更高的执行效率, 向量化写法仅仅是可以使得程序比较简洁。

均值、标准差这些基本统计函数已经在Julia的Statistics标准库中有定义。

B.7 程序控制结构

B.7.1 复合表达式

begin ... end可以将多行的多个表达式组合起来当作一个表达式, 复合表达式的值是其中最后一个表达式的值。 如

z = begin
  x = 1
  y = 2
  x + y
end
z
3

多个表达式也可以用分号分隔后写在圆括号中,作为一个复合表达式,如

z = (x = 1; y = 2; x + y)
z
3

B.7.2 比较运算

两个数值之间用如下的比较运算符进行比较:

==   !=   <   <=   >   >=

分别表示等于、不等于、小于、小于等于、大于、大于等于。 要特别注意“等于”比较用两个等号表示。

比较的结果是true(真值)或者false(假值)。 结果类型为Bool类型(布尔型)。

1 == 1.0
true
2 != 2.0001
true
3.5 > -1
true
3.5 > -1.5
true
-3.5 < 1.2
true
-3.5 <= 1.2
true
-3.5 > 1.2
false
-3.5 >= 1.2
false

两个字符串之间也可以比较, 比较时按字典序比较, 两个字符的次序按照其Unicode编码值比较。如

"abc" == "ABC"
false
"ab" < "abc"
true
"陕西省" != "山西省"
true

B.7.3 逻辑运算

比较通常有变量参与。如

age = 35; sex="F"
age < 18
false
sex == "F"
true

有时需要构造复合的条件, 如“年龄不足18岁且性别为女”, “年龄在18岁以上或者性别为男”等。

&&表示要求两个条件同时成立, 用||表示只要两个条件之一成立则结果为真, 用!cond表示cond的反面。 如

age < 18 && sex == "F"
false
age >= 18 || sex == "M"
true

B.7.4 短路与运算和分支

&& 是一种短路运算, 表达式cond && expr 仅当cond为true时才计算(运行)expr, 所以这种写法经常用作程序分支的简写: 条件cond为真时执行expr, 否则不执行。

比如,在计算x的平方根之前,先判断其非负:

x = -1.44
x < 0 && println("平方根计算:自变量定义域错误,x=", x)
平方根计算:自变量定义域错误,x=-1.44

B.7.5 短路或运算与分支

||是一种短路或运算,表达式cond || expr 仅当cond为false时才计算(运行)expr, 所以这种写法经常作为程序分支的缩写: 条件cond为假时才执行expr,否则不执行。

比如,求平方根时当自变量不为负时才计算平方根:

x < 0 || (y = sqrt(x))
true

B.7.6 if–end结构

可以用if cond ... end结构在条件cond成立时才执行某些语句,如

x = 1.44
if x >= 0
  y = sqrt(x)
  println("√", x, " = ", y)
end
√1.44 = 1.2

注意条件不需要用括号包围,结构以end语句结尾。

B.7.7 if–else–end结构

if cond ... else ... end结构当条件成立时执行第一个分支中的语句, 当条件不成立时执行第二个分支中的语句。 如

x = -1.44
if x >= 0
  y = sqrt(x)
  println("√", x, " = ", y)
else
  y = sqrt(-x)
  println("√", x, " = ", y, "i")
end
√-1.44 = 1.2i

B.7.8 if-elseif-else-end结构

if cond1 ... elseif cond2 ... else ... end可以有多个分支, 有多个条件cond1, cond2, ……, 依次判断各个条件,那个条件成立就执行对应分支的语句, 所有条件都不成立则执行else分支的语句。 条件cond2隐含条件cond1不成立, cond3隐含cond1cond2都不成立, 依此类推。 例如

age = 35
if age < 18
  println("未成年")
elseif age < 60
  println("中青年")
elseif age < 100
  println("老年")
else
  println("老寿星!")
end
中青年

B.7.9 三元运算符

可以用cond ? expr1 : expr2表示比较简单的两分支选择, 当cond成立时结果为expr1的结果, 当cond不成立时结果为expr2的结果。 如

x = -1.44
y = x >= 0 ? sqrt(x) : sqrt(-x)
1.2

B.7.10 for循环

for循环一般是沿着某个范围进行计算或处理,格式如下:

for loopvar = a:b
  expr1
  expr2
  ...
end

其中loopvar是自己命名的循环变量名, for结构块内的语句(表达式)先对loopvar=a运行, 再对loopvar=a+1运行, 最后对loopvar=b运行,然后结束。 如

for i=1:3
  y = i^3
  println(i, "^3 = ", y)
end
1^3 = 1
2^3 = 8
3^3 = 27

范围也可以是1:2:9, 0:0.1:1这样的带有跨度(增量)的, 可以是倒数的如3:-1:1表示3, 2, 1。

B.7.11 对向量元素循环

in关键字,可以使得循环变量遍历某个向量的元素,如:

x = [2, 3, 5, 7, 11]
for i in x
  y = i^3
  println(i, "^3 = ", y)
end
2^3 = 8
3^3 = 27
5^3 = 125
7^3 = 343
11^3 = 1331

B.7.12 向量元素按下标循环

x是一个向量,上面的做法可以遍历x每个元素。 有时还需要按照向量的下标遍历,这时使用eachindex(x),如

x = [2, 3, 5, 7, 11]
for i in eachindex(x)
  println("Prime No. ", i, " = ", x[i])
end
Prime No. 1 = 2
Prime No. 2 = 3
Prime No. 3 = 5
Prime No. 4 = 7
Prime No. 5 = 11

B.7.13 列表推导

对向量的循环,经常可以表达成一种称为comprehension的语法, 译为列表推导。 例如,为了生成1, 2, 3的立方的向量,可以写成

xcube = [i^3 for i=1:3]
3-element Array{Int64,1}:
  1
  8
 27

对前5个素数作立方,可以写成

x = [2, 3, 5, 7, 11]
y = [z^3 for z in x]
5-element Array{Int64,1}:
    8
   27
  125
  343
 1331

B.7.14 两重for循环

for循环可以嵌套,如

for i=1:9
  for j=1:i
    print(i, "×", j, " = ", i*j, "  ")
  end
  println()
end
1×1 = 1  
2×1 = 2  2×2 = 4  
3×1 = 3  3×2 = 6  3×3 = 9  
4×1 = 4  4×2 = 8  4×3 = 12  4×4 = 16  
5×1 = 5  5×2 = 10  5×3 = 15  5×4 = 20  5×5 = 25  
6×1 = 6  6×2 = 12  6×3 = 18  6×4 = 24  6×5 = 30  6×6 = 36  
7×1 = 7  7×2 = 14  7×3 = 21  7×4 = 28  7×5 = 35  7×6 = 42  7×7 = 49  
8×1 = 8  8×2 = 16  8×3 = 24  8×4 = 32  8×5 = 40  8×6 = 48  8×7 = 56  8×8 = 64  
9×1 = 9  9×2 = 18  9×3 = 27  9×4 = 36  9×5 = 45  9×6 = 54  9×7 = 63  9×8 = 72  9×9 = 81  

这种两重循环可以简写为一个for语句,外层循环先写, 内层循环后写,中间用逗号分隔。 如

for i=1:9, j=1:i
  print(i, "×", j, " = ", i*j, "  ")
  j==i && println()
end
1×1 = 1  
2×1 = 2  2×2 = 4  
3×1 = 3  3×2 = 6  3×3 = 9  
4×1 = 4  4×2 = 8  4×3 = 12  4×4 = 16  
5×1 = 5  5×2 = 10  5×3 = 15  5×4 = 20  5×5 = 25  
6×1 = 6  6×2 = 12  6×3 = 18  6×4 = 24  6×5 = 30  6×6 = 36  
7×1 = 7  7×2 = 14  7×3 = 21  7×4 = 28  7×5 = 35  7×6 = 42  7×7 = 49  
8×1 = 8  8×2 = 16  8×3 = 24  8×4 = 32  8×5 = 40  8×6 = 48  8×7 = 56  8×8 = 64  
9×1 = 9  9×2 = 18  9×3 = 27  9×4 = 36  9×5 = 45  9×6 = 54  9×7 = 63  9×8 = 72  9×9 = 81  

B.7.15 矩阵元素遍历

矩阵元素按照行列下标遍历,可以写成两重循环的形式。 Julia的矩阵是按列存储的, 所以循环时先对第一列各个元素循环, 再对第二列各个元素循环, ……,按这样的次序遍历是比较有效的。 如

A1 = [1 2 3; 4 5 6]
for j=1:3, i=1:2
  println("A1[", i, ", ", j, "] = ", A1[i,j])
end
A1[1, 1] = 1
A1[2, 1] = 4
A1[1, 2] = 2
A1[2, 2] = 5
A1[1, 3] = 3
A1[2, 3] = 6

for结构的两重循环中写在前面的是外层循环, 循环变量变化较慢, 写在后面的是内层循环,循环变量变化较快。 上例中列下标j写在前面,行下标i写在后面, 所以关于列的循环j是外层循环,关于行的循环i是内层循环, 这样的矩阵元素遍历方式是按列次序遍历。

B.7.16 矩阵的列表推导

在方括号内用两重的循环变量遍历可以定义矩阵。如

Ac = [i*100 + j for i=1:2, j=1:3]
2×3 Array{Int64,2}:
 101  102  103
 201  202  203

这里写在前面的循环变量i对应于行下标, 写在后面的循环变量j对应于列下标。 执行时行下标i在内层循环,列下标j在外层循环。

又如,如下程序返回矩阵对角化的结果:

[(i==j ? Ac[i,i] : 0) for i=1:2, j=1:3]
2×3 Array{Int64,2}:
 101    0  0
   0  202  0

B.7.17 while循环

for循环适用于对固定的元素或者下标的遍历, 在预先未知具体循环次数时,需要使用当型循环循环或者直到型循环。

Julia中用while cond ... end表示当型循环, 在条件cond成立时执行结构内的语句, 直到cond不成立时不再循环。

例如,要判断一个奇数x是否素数, 从3开始逐个用奇数去除x,直到除尽或者除数等于x为止:

x = 1333333
i = 3
while i < x && x % i != 0
    i += 2
end
if i == x
    println(x, "is prime.")
else
    println(x, "=", i, " X ", x ÷ i)
end
1333333=23 X 57971

B.7.18 直到型循环与break语句

当型循环每次进入循环之前判断循环条件是否成立, 成立才进入循环。

直到型循环每次先进入循环,在循环末尾判断循环退出条件是否满足, 满足退出条件时就不再循环。 Julia语言没有提供专门的直到型循环语法,可以用如下的方法制作直到型循环:

while true
  expr1
  expr2
  ...
  cond && break
end

其中cond是循环退出条件。break语句表示退出一重循环。

例如,用泰勒展开近似计算自然对数\(\log(1 + x)\)\[ \log(1 + x) = x + \sum_{k=2}^\infty (-1)^{k-1} \frac{x^k}{k} \]

实际计算时不可能计算无穷次,所以指定一个精度如eps=0.0001, 当计算的通项小于此精度时停止计算。 程序用直到型型循环写成:

eps = 0.0001
x = 1.0
y = x; xk = x; sgn = 1; k = 1
while true
  k += 1; sgn *= -1; xk *= x
  item = xk / k
  y += sgn*item
  item < eps && break
end
println("eps = ", eps, " log(1+", x, ") = ", y, 
  " Iterations: ", k)
eps = 0.0001 log(1+1.0) = 0.6931971730609582 Iterations: 10001