Introducing Julia/Controlling the flow


« Introducing Julia
Controlling the flow
»
Types Functions

控制流程的不同方法

编辑

通常,Julia程序的每一行都是依次计算的。有多种方法可以控制和修改评估流程。它们与其他语言中使用的结构相对应:


  • 三元 复合表达式
  • 布尔转换表达式
  • if elseif else end - 条件求值
  • for end - 迭代求值
  • while end - 迭代条件求值
  • try catch error throw 异常处理
  • do 代码块

三元表达式

编辑

通常,如果某个条件为真,您会希望执行job A (或调用函数A),如果不是,则希望执行 job B(函数B)。写流程控制最快的方法是使用三元运算符("?" 和 ":"):

julia> x = 1
1
julia> x > 3 ? "yes" : "no"
"no"
julia> x = 5
5
julia> x > 3 ? "yes" : "no"
"yes"

下面是另一个例子:

julia> x = 0.3
0.3
julia> x < 0.5 ? sin(x) : cos(x)
0.29552020666133955

Julia 返回 sin(x) 的值, 因为 x 小于 0.5. cos(x) 根本没有被计算求值。

布尔转换表达式

编辑

布尔运算符使您可以在条件为真时对表达式求值。可以使用 &&|| 组合条件和表达式。&& 表示“和”,而 || 表示“或”。由于 Julia 逐个计算表达式,因此仅当前一个条件为 true 或 false 时,才能轻松安排对表达式求值。

下面的示例使用一个 Julia 函数,该函数根据数字是否为奇数返回 true或 false :isodd(n)


对于 &&,这两个部分都必须是 true ,所以我们可以这样写:


julia> isodd(1000003) && @warn("That's odd!")
WARNING: That's odd!

julia> isodd(1000004) && @warn("That's odd!")
false

如果第一个条件(数字为奇数)为真,则计算第二个表达式。如果第一个不为真,则不对表达式求值,只返回条件。


另一方面,使用 || 运算符:

julia> isodd(1000003) || @warn("That's odd!")
true

julia> isodd(1000004) || @warn("That's odd!")
WARNING: That's odd!

如果第一个条件为 true,则不需要对第二个表达式求值,因为我们已经有“或”所需的一个真值,并且它返回值true。如果第一个条件为false,则对第二个表达式求值,因为该表达式可能被证明为真。

这类求值也称为“短路求值”。

If and Else

编辑

对于更一般和传统的条件执行方法,您可以使用 if, elseifelse。如果您习惯使用其他语言,请不要担心空格、大括号、缩进、括号、分号或诸如此类的东西,但请记住使用end完成条件的构造。

name = "Julia"
if name == "Julia"
   println("I like Julia")
elseif name == "Python"
   println("I like Python.")
   println("But I prefer Julia.")
else
   println("I don't know what I like")
end

elseifelse 的这部分是可选的:

name = "Julia"
if name == "Julia"
   println("I like Julia")
end

别忘了 end!

"switch"和"case"语句呢?emmmm,你不需要学习这些语法,因为他们不存在!

ifelse

编辑

有一个 ifelse 函数。看起来是这样的:

julia> s = ifelse(false, "hello", "goodbye") * " world"

ifelse 是一个普通函数,它计算所有参数,并根据第一个参数的值返回第二个或第三个参数。使用条件 if? ... :,只对所选路由中的表达式求值。或者,也可以这样:

julia> x = 10
10
julia> if x > 0
          "positive"
       else
           "negative or zero"
       end
"positive"
julia> r = if x > 0
          "positive"
       else
          "negative or zero"
       end
"positive"
                                     
julia> r
"positive"

for循环 和 迭代

编辑

遍历一个列表或一组值或从起始值到终止值都是迭代的样例,而 for ... end 构造可以让您遍历许多不同类型的对象,包括范围、数组、集、字典和字符串。

下面是通过一系列值进行简单迭代的标准语法:

julia> for i in 0:10:100
            println(i)
       end
0
10
20
30
40
50
60
70
80
90
100

变量 i 依次获取数组中每个元素的值(它是从 Range 对象构建的):这里以10为步长从0到100。

julia> for color in ["red", "green", "blue"] # an array
           print(color, " ")
       end
red green blue
julia> for letter in "julia" # a string
           print(letter, " ")
       end
j u l i a
julia> for element in (1, 2, 4, 8, 16, 32) # a tuple
           print(element, " ")
       end
1 2 4 8 16 32
julia> for i in Dict("A"=>1, "B"=>2) # a dictionary
           println(i)
       end
"B"=>2
"A"=>1
julia> for i in Set(["a", "e", "a", "e", "i", "o", "i", "o", "u"])
           println(i)
       end
e
o
u
a
i

我们还没有见过集和字典,但是遍历它们是完全一样的。

可以遍历二维数组,从上到下逐步“向下”遍历第1列,然后遍历第2列,依此类推:

julia> a = reshape(1:100, (10, 10))
10x10 Array{Int64,2}:
 1  11  21  31  41  51  61  71  81   91
 2  12  22  32  42  52  62  72  82   92
 3  13  23  33  43  53  63  73  83   93
 4  14  24  34  44  54  64  74  84   94
 5  15  25  35  45  55  65  75  85   95
 6  16  26  36  46  56  66  76  86   96
 7  17  27  37  47  57  67  77  87   97
 8  18  28  38  48  58  68  78  88   98
 9  19  29  39  49  59  69  79  89   99
10  20  30  40  50  60  70  80  90  100
julia> for n in a
           print(n, " ")
       end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

您可以使用 = 代替 in.

迭代数组并更新
编辑

当您在数组上迭代时,每次循环中都会检查数组,以防它发生更改。一个你应该避免犯的错误是使用 push! 使数组在循环中增长。仔细运行以下文本,当您看到足够多的内容时准备使用 Ctrl-C(否则您的计算机最终会崩溃):

julia> c = [1]
1-element Array{Int64,1}:
1
 
julia> for i in c
          push!(c, i)
          @show c
          sleep(1)
      end

c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...

循环变量和作用域

编辑

遍历每个项目的变量,即“循环变量”,仅存在于循环内部,并在循环结束后立即消失。

julia> for i in 1:10
         @show i
       end
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10

julia> i
ERROR: UndefVarError: i not defined

如果要记住循环之外的循环变量的值(例如,如果必须退出循环并需要知道所达到的值),请使用 global 关键字定义一个比循环更持久的变量。

julia> for i in 1:10
         global howfar 
         if i % 4 == 0 
            howfar = i 
         end 
       end 
julia> howfar
8

在这里,howfar 在循环之前是不存在的,但是当循环结束时,它存活了下来,讲述了它的故事。如果 howfar 在循环开始之前已存在,则仅当在循环中使用全局时才能更改其值。

在REPL中工作与在函数中编写代码略有不同。在函数中,您可以这样写:

function f()
    howfar = 0
    for i in 1:10
        if i % 4 == 0 
            howfar = i 
        end 
    end 
    return howfar
end

@show f()
8

循环中声明的变量

编辑

类似地,如果您在循环中声明一个新变量,则该变量在循环完成后将不复存在。在此示例中,k 是在以下位置创建的:

julia> for i in 1:5
          k = i^2 
          println("$(i) squared is $(k)")
       end 
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25

因此它在循环完成后不存在:

julia> k
ERROR: UndefVarError: k not defined

在循环的一个迭代中创建的变量在每次迭代结束时都会被忘记。在此循环中:

for i in 1:10
    z = i
    println("z is $z")
end
z is 1
z is 2
z is 3
z is 4
z is 5
z is 6
z is 7
z is 8
z is 9
z is 10

每次都会重新创建 z。如果希望变量从一个迭代持续到另一个迭代,它必须是全局的:

julia> counter = 0
0

julia> for i in 1:10
               global counter
               counter += i
           end 

julia> counter
55

要更详细地了解这一点,请考虑下面的代码。

for i in 1:10
    if ! @isdefined z
        println("z isn't defined")
    end
    z = i
    println("z is $z")
end

也许您只期望第一个循环产生“z未定义的错误”?事实上,即使 z 是在循环体中创建的,在下一次迭代开始时也没有定义它。

z isn't defined
z is 1
z isn't defined
z is 2
z isn't defined
z is 3
z isn't defined
z is 4
z isn't defined
z is 5
z isn't defined
z is 6
z isn't defined
z is 7
z isn't defined
z is 8
z isn't defined
z is 9
z isn't defined
z is 10

再说一遍,使用 global 关键字强制 z 在创建后在循环之外可用:

for i in 1:10
    global z
    if ! @isdefined z
        println("z isn't defined")
    else
        println("z was $z")
    end
    z = i
    println("z is $z")
end
z isn't defined
z is 1
z was 1
z is 2
z was 2
...
z is 9
z was 9
z is 10

如果您在全局范围内工作,虽然 z 现在任何地方都可用,值为10。

although, if you're working in global scope, z is now available everywhere, with the value 10.

这种行为是因为我们在 REPL 工作。通常更好的做法是将代码放在函数中,在函数中不需要将从循环外部继承的变量标记为全局变量:

function f()
   counter = 0
   for i in 1:10
      counter += i
   end
   return counter
end
julia> f()
55

循环微调:continue

编辑

有时,在特定的迭代中,您可能希望跳到下一个值。您可以使用 continue 跳过循环中的其余代码,并使用下一个值再次启动循环。

for i in 1:10
    if i % 3 == 0
       continue
    end
    println(i) # this and subsequent lines are
               # skipped if i is a multiple of 3
end

1
2
4
5
7
8
10

推导

编辑

这一概念只是产生和收集项目的一种方式。在数学界,你会这样说:

"设S是所有元素n的集合,其中n大于或等于1,小于或等于10。"

在Julia中,您可以这样写:

julia> S = Set([n for n in 1:10])
Set([7,4,9,10,2,3,5,8,6,1])

[n for n in 1:10] 的结构被称为 数组推导列表推导 ("comprehension",意思是“得到一切”,而不是“理解”)。外面的方括号将通过 计算放置在 for 迭代之前的表达式 而生成的元素集合在一起。而不是end ,使用方括号完成。

julia> [i^2 for i in 1:10]
10-element Array{Int64,1}:
  1
  4
  9
 16
 25
 36
 49
 64
 81
100

可以指定元素的类型:

julia> Complex[i^2 for i in 1:10]
10-element Array{Complex,1}:
  1.0+0.0im
  4.0+0.0im
  9.0+0.0im
 16.0+0.0im
 25.0+0.0im
 36.0+0.0im
 49.0+0.0im
 64.0+0.0im
 81.0+0.0im
100.0+0.0im

但是 Julia 可以计算出你所产生的结果的类型:

julia> [(i, sqrt(i)) for i in 1:10]
10-element Array{Tuple{Int64,Float64},1}:
(1,1.0)
(2,1.41421)
(3,1.73205)
(4,2.0)
(5,2.23607)
(6,2.44949)
(7,2.64575)
(8,2.82843)
(9,3.0)
(10,3.16228)

下面是如何通过推导来生成字典:

julia> Dict(string(Char(i + 64)) => i for i in 1:26)
Dict{String,Int64} with 26 entries:
 "Z" => 26
 "Q" => 17
 "W" => 23
 "T" => 20
 "C" => 3
 "P" => 16
 "V" => 22
 "L" => 12
 "O" => 15
 "B" => 2
 "M" => 13
 "N" => 14
 "H" => 8
 "A" => 1
 "X" => 24
 "D" => 4
 "G" => 7
 "E" => 5
 "Y" => 25
 "I" => 9
 "J" => 10
 "S" => 19
 "U" => 21
 "K" => 11
 "R" => 18
 "F" => 6

接下来,这里是一个理解中的两个迭代器,用逗号分隔,这使得生成表变得非常容易。在这里,我们将创建一个元组表格:

julia> [(r,c) for r in 1:5, c in 1:2]
5×2 Array{Tuple{Int64,Int64},2}:
(1,1)  (1,2)
(2,1)  (2,2)
(3,1)  (3,2)
(4,1)  (4,2)
(5,1)  (5,2)

r 经过五个循环,每个 c 的值对应一个循环。 嵌套循环 的工作方式正好相反。此处将遵守列主次顺序,如阵列中填充了纳秒时间值时所示:

julia> [Int(time_ns()) for r in 1:5, c in 1:2]
5×2 Array{Int64,2}:
1223184391741562  1223184391742642
1223184391741885  1223184391742817
1223184391742067  1223184391743009
1223184391742256  1223184391743184
1223184391742443  1223184391743372

您还可以提供一个测试表达式来过滤产品。例如,生成 1 到 100 之间可被 7 整除的所有整数:

julia> [x for x in 1:100 if x % 7 == 0]
14-element Array{Int64,1}:
  7
 14
 21
 28
 35
 42
 49
 56
 63
 70
 77
 84
 91
 98
生成表达式
编辑

与推导类似,生成器表达式可用于通过迭代变量来生成值,但与理解不同的是,这些值是按需生成的。

julia> sum(x^2 for x in 1:10)
385
julia> collect(x for x in 1:100 if x % 7 == 0)
14-element Array{Int64,1}:
  7
 14
 21
 28
 35
 42
 49
 56
 63
 70
 77
 84
 91
 98

枚举数组

编辑

通常,您希望逐个元素检查数组元素,同时跟踪每个元素的索引号。enumerate() 函数的作用是:生成一个索引号和每个索引号的值,从而为您提供一个可迭代版本的内容:

julia> m = rand(0:9, 3, 3)
3×3 Array{Int64,2}:
6  5  3
4  0  7
1  7  4

julia> [i for i in enumerate(m)]
3×3 Array{Tuple{Int64,Int64},2}:
(1, 6)  (4, 5)  (7, 3)
(2, 4)  (5, 0)  (8, 7)
(3, 1)  (6, 7)  (9, 4)

在循环的每次迭代中检查数组是否可能发生更改。

数组压缩

编辑

有时,您希望同时处理两个或更多个数组,先取每个数组的第一个元素,然后再取第二个,依此类推。使用名字挺好的 zip() 函数可以做到这一点:

julia> for i in zip(0:10, 100:110, 200:210)
           println(i) 
end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

如果数组的大小不同,你会认为它们都会出错。如果第三个数组太大或太小怎么办?

julia> for i in zip(0:10, 100:110, 200:215)
           println(i)
       end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

但 Julia 没有被耍-任何一个数组中的任何一个供过于求或供过于求都会得到很好的处理。

julia> for i in zip(0:15, 100:110, 200:210)
           println(i)
       end
(0,100,200)
(1,101,201)
(2,102,202)
(3,103,203)
(4,104,204)
(5,105,205)
(6,106,206)
(7,107,207)
(8,108,208)
(9,109,209)
(10,110,210)

但是,这在填充数组的情况下不起作用,在这种情况下,维度必须匹配:

(v1.0) julia> [i for i in zip(0:4, 100:102, 200:202)]
ERROR: DimensionMismatch("dimensions must match")
Stacktrace:
 [1] promote_shape at ./indices.jl:129 [inlined]
 [2] axes(::Base.Iterators.Zip{UnitRange{Int64},Base.Iterators.Zip2{UnitRange{Int64},UnitRange{Int64}}}) at ./iterators.jl:371
 [3] _array_for at ./array.jl:611 [inlined]
 [4] collect(::Base.Generator{Base.Iterators.Zip{UnitRange{Int64},Base.Iterators.Zip2{UnitRange{Int64},UnitRange{Int64}}},getfield(Main, Symbol("##5#6"))}) at ./array.jl:624
 [5] top-level scope at none:0
(v1.0) julia> [i for i in zip(0:2, 100:102, 200:202)]
3-element Array{Tuple{Int64,Int64,Int64},1}:
 (0, 100, 200)
 (1, 101, 201)
 (2, 102, 202)

可迭代对象

编辑

对于可以遍历的所有内容,“For Something in Something”结构都是一样的:数组、字典、字符串、集合、范围等等。在Julia中,这是一个普遍的原则:有许多方法可以创建“可迭代对象”,该对象被设计为作为迭代过程的一部分,一次提供一个元素。

我们已经遇到的最明显的例子是Range对象。当您在REPL中键入它时,它看起来并不多:

julia> ro = 0:2:100
0:2:100

但是当你开始遍历它时,它会给出数字:

julia> [i for i in ro]
51-element Array{Int64,1}:
  0
  2
  4
  6
  8
 10
 12
 14
 16
 18
 20
 22
 24
 26
 28
  ⋮
 74
 76
 78
 80
 82
 84
 86
 88
 90
 92
 94
 96
 98
100

如果需要数组中某个范围(或其他可迭代对象)中的数字,可以使用 collect()将其收集起来:

julia> collect(0:25:100)
5-element Array{Int64,1}:
  0
 25
 50
 75
100

您不必收集可迭代对象的每个元素,只需对其进行迭代即可。当您有其他Julia函数创建的可迭代对象时,这一点特别有用。例如,permutations()创建一个可迭代对象,该对象包含数组的所有排列。当然,您可以使用collect()获取它们并创建一个新数组:

julia> collect(permutations(1:4))
24-element Array{Array{Int64,1},1}:
 [1,2,3,4]
 [1,2,4,3]
 
 [4,3,2,1]

但是在任何大的东西上,都有成百上千的排列。这就是为什么迭代器对象不能同时从迭代中产生所有值的原因:内存和性能。Range 对象不会占用太多空间,即使遍历它可能需要很长时间,具体取决于范围有多大。如果您一次生成所有的数字,而不是仅在需要时才生成它们,那么它们都必须存储在某个地方,直到您需要使用它们…

Julia为处理其他类型的数据提供了可迭代的对象。例如,在处理文件时,可以将打开的文件视为可迭代对象:

 filehandle = "/Users/me/.julia/logs/repl_history.jl"
 for line in eachline(filehandle)
     println(length(line), line)
 end
使用 eachindex()
编辑

迭代数组时的常见模式是对 i 的每个值执行某些任务,其中 i 是每个元素的索引号,而不是元素:

 for i = 1:length(A) 
   # do something with i or A[i]
 end

有一种更有效(即在某些情况下更快)执行此操作的方法:

 for i in eachindex(A) 
   # do something with i or A[i]
 end

更多迭代器

编辑

有一个名为 IterTools.jl 的 Julia 包,它提供了一些高级迭代器函数。

(v1.0) pkg> ] add IterTools
julia> using IterTools

例如,partition()将迭代器中的对象分组到易于处理的块中:

julia> collect(partition(1:10, 3, 1))
8-element Array{Tuple{Int64,Int64,Int64},1}:
(1, 2, 3) 
(2, 3, 4) 
(3, 4, 5) 
(4, 5, 6) 
(5, 6, 7) 
(6, 7, 8) 
(7, 8, 9) 
(8, 9, 10)

chain() 一个接一个地处理所有迭代器:

 for i in chain(1:3, ['a', 'b', 'c'])
   @show i
end

 i = 1
 i = 2
 i = 3
 i = 'a'
 i = 'b'
 i = 'c'

subsets() 处理对象的所有子集。可以指定大小:

 for i in subsets(collect(1:6), 3)
   @show i
end

 i = [1,2,3]
 i = [1,2,4]
 i = [1,2,5]
 i = [1,2,6]
 i = [1,3,4]
 i = [1,3,5]
 i = [1,3,6]
 i = [1,4,5]
 i = [1,4,6]
 i = [1,5,6]
 i = [2,3,4]
 i = [2,3,5]
 i = [2,3,6]
 i = [2,4,5]
 i = [2,4,6]
 i = [2,5,6]
 i = [3,4,5]
 i = [3,4,6]
 i = [3,5,6]
 i = [4,5,6]

嵌套循环

编辑

如果希望将一个循环嵌套在另一个循环中,则不必复制 forend 关键字。只要用逗号:

julia> for x in 1:10, y in 1:10
          @show (x, y)
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (1,4)
(x,y) = (1,5)
(x,y) = (1,6)
(x,y) = (1,7)
(x,y) = (1,8)
(x,y) = (1,9)
(x,y) = (1,10)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (2,4)
(x,y) = (2,5)
(x,y) = (2,6)
(x,y) = (2,7)
(x,y) = (2,8)
(x,y) = (2,9)
(x,y) = (2,10)
(x,y) = (3,1)
(x,y) = (3,2)
...
(x,y) = (9,9)
(x,y) = (9,10)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)
(x,y) = (10,4)
(x,y) = (10,5)
(x,y) = (10,6)
(x,y) = (10,7)
(x,y) = (10,8)
(x,y) = (10,9)
(x,y) = (10,10)

(十分有用的@show 宏打印出东西的名称及其值。)

较短和较长形式的嵌套循环之间的一个不同之处是 break

julia> for x in 1:10
          for y in 1:10
              @show (x, y)
              if y % 3 == 0
                 break
              end
          end
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (3,1)
(x,y) = (3,2)
(x,y) = (3,3)
(x,y) = (4,1)
(x,y) = (4,2)
(x,y) = (4,3)
(x,y) = (5,1)
(x,y) = (5,2)
(x,y) = (5,3)
(x,y) = (6,1)
(x,y) = (6,2)
(x,y) = (6,3)
(x,y) = (7,1)
(x,y) = (7,2)
(x,y) = (7,3)
(x,y) = (8,1)
(x,y) = (8,2)
(x,y) = (8,3)
(x,y) = (9,1)
(x,y) = (9,2)
(x,y) = (9,3)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)

julia> for x in 1:10, y in 1:10
          @show (x, y)
         if y % 3 == 0
           break
         end
       end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)

请注意,break以较短的形式出现在内部循环和外部循环中,而在较长的形式中仅出现在内部循环中。

优化嵌套循环

编辑

对于Julia,内部循环应该关注行而不是列。这是由于数组如何存储在内存中。例如,在这个Julia数组中,单元格1、2、3和4彼此相邻存储在内存中(“列优先”格式)。因此,从1到2到3向下移动列比沿行移动要快,因为从列到列从1到5到9跳过需要额外的计算:

+-----+-----+-----+--+
|  1  |  5  |  9  |
|     |     |     |
+--------------------+
|  2  |  6  |  10 |
|     |     |     |
+--------------------+
|  3  |  7  |  11 |
|     |     |     |
+--------------------+
|  4  |  8  |  12 |
|     |     |     |
+-----+-----+-----+--+

下面的示例由简单的循环组成,但是行和列的迭代方式不同。“不好”的版本逐列查看第一行,然后向下移动到下一行,依此类推。

function laplacian_bad(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr, nc = size(x)
    for ir = 2:nr-1, ic = 2:nc-1 # bad loop nesting order
        lap_x[ir, ic] =
            (x[ir+1, ic] + x[ir-1, ic] +
            x[ir, ic+1] + x[ir, ic-1]) - 4*x[ir, ic]
    end
end

在“好的”版本中,两个循环被正确嵌套,以便内部循环按照数组的内存布局在行中向下移动:

function laplacian_good(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr,nc = size(x)
    for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
        lap_x[ir,ic] =
            (x[ir+1,ic] + x[ir-1,ic] +
            x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
    end
end

另一种提高速度的方法是使用宏@inbounds删除数组边界检查:

function laplacian_good_nocheck(lap_x::Array{Float64,2}, x::Array{Float64,2})
    nr,nc = size(x)
    for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
        @inbounds begin lap_x[ir,ic] = # no array bounds checking
            (x[ir+1,ic] +  x[ir-1,ic] +
            x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
        end
    end
end

下面是测试函数:

function main_test(nr, nc)
    field = zeros(nr, nc)
    for ic = 1:nc, ir = 1:nr
        if ir == 1 || ic == 1 || ir == nr || ic == nc
            field[ir,ic] = 1.0
        end
    end
    lap_field = zeros(size(field))

    t = @elapsed laplacian_bad(lap_field, field)
    println(rpad("laplacian_bad", 30), t)
    
    t = @elapsed laplacian_good(lap_field, field)
    println(rpad("laplacian_good", 30), t)
    
    t = @elapsed laplacian_good_nocheck(lap_field, field)
    println(rpad("laplacian_good no check", 30), t)
end

结果表明,仅根据行/列扫描顺序就可以得到不同的性能差异。“不检查”的版本更快.。

julia> main_test(10000,10000)
laplacian_bad                 1.947936034
laplacian_good                0.190697149
laplacian_good no check       0.092164871

创建自用的可迭代对象

编辑

可以设计自己的可迭代对象。定义类型时,需要向 Julia 的 iterate()函数添加几个方法。那你就可以用类似于for .. end循环用于处理对象的组件,这些iterate()方法将在必要时自动调用。


下面的示例演示如何创建一个可迭代对象,该对象生成将大写字母与1到9之间的数字组合在一起的字符串序列。因此,我们的序列中的第一个项目是“A1”,然后是“A2”、“A3”,直到“A9”,然后是“B1”、“B2”,等等,最后是“Z9”。

首先,我们将定义一个名为 SN (StringNumber)的新类型:

mutable struct SN
    str::String
    num::Int64
end

稍后,我们将使用如下内容创建此类型的可迭代对象:

sn = SN("A", 1)

迭代器将生成到“Z9”的所有字符串。


现在,我们必须向 iterate()函数添加两个方法。这个函数已经存在于 Julia 中(这就是为什么您可以迭代所有的基本数据对象),需要Base前缀:我们将向现有的iterate()函数添加一个新的用于处理这些特殊对象的方法。


第一个方法不接受参数(类型除外),用于启动迭代过程。

function Base.iterate(sn::SN)
    str = sn.str 
    num = sn.num

    if num == 9
        nextnum = 1
        nextstr = string(Char(Int(str[1])) + 1)    
    else
        nextnum = num + 1
        nextstr = str
    end

    return (sn, SN(nextstr, nextnum))
end

这将返回一个元组:我们计算过的迭代器的第一个值和未来的值(以防万一我们希望在“A1”以外的位置启动迭代器)。


iterate()的第二个方法接受两个参数:可迭代对象和当前状态。它再次返回包含两个值的元组,即下一项和下一状态。但是首先,如果没有更多的值可用,iterate()函数应该什么也不会返回。

function Base.iterate(sn::SN, state)

    # check if we've finished?
    if state.str == "[" # when Z changes to [ we're done
        return 
    end 

    # we haven't finished, so we'll use the incoming one immediately
    str = state.str
    num = state.num

    # and prepare the one after that, to be saved for later
    if num == 9
        nextnum = 1
        nextstr = string(Char(Int(str[1])) + 1)    
    else
        nextnum = num + 1
        nextstr = state.str
    end

    # return: the one to use next, the one after that
    return (SN(str, num), SN(nextstr, nextnum))
end

告诉迭代器什么时候完成很简单,因为一旦传入状态包含“[”我们已经完成了,因为“[”(91)的代码紧跟在“Z”(90)的代码之后。


通过添加这两个方法来处理 SN 类型,现在可以对它们进行迭代。为其他几个 Base 函数添加一些方法也很有用,例如show()length()length()方法计算出从 sn 开始还有多少 SN 字符串可用。

Base.show(io::IO, sn::SN) = print(io, string(sn.str, sn.num))

function Base.length(sn::SN) 
    cn1 = Char(Int(Char(sn.str[1]) + 1)) 
    cnz = Char(Int(Char('Z')))
    (length(cn1:cnz) * 9) + (10 - sn.num)
end

迭代器现在可以使用了:

julia> sn = SN("A", 1)
A1

julia> for i in sn
          @show i 
       end 
i = A1
i = A2
i = A3
i = A4
i = A5
i = A6
i = A7
i = A8
...
i = Z6
i = Z7
i = Z8
i = Z9
julia> for sn in SN("K", 9)
           print(sn, " ") 
       end
K9 L1 L2 L3 L4 L5 L6 L7 L8 L9 M1 M2 M3 M4 M5 M6 M7 M8 M9 N1 N2 N3 N4 N5 N6 N7 N8
N9 O1 O2 O3 O4 O5 O6 O7 O8 O9 P1 P2 P3 P4 P5 P6 P7 P8 P9 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8
Q9 R1 R2 R3 R4 R5 R6 R7 R8 R9 S1 S2 S3 S4 S5 S6 S7 S8 S9 T1 T2 T3 T4 T5 T6 T7 T8
T9 U1 U2 U3 U4 U5 U6 U7 U8 U9 V1 V2 V3 V4 V5 V6 V7 V8 V9 W1 W2 W3 W4 W5 W6 W7 W8
W9 X1 X2 X3 X4 X5 X6 X7 X8 X9 Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 Y9 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8
Z9
julia> collect(SN("Q", 7)),
(Any[Q7, Q8, Q9, R1, R2, R3, R4, R5, R6, R7  …  Y9, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9],)

While 循环

编辑

若要在条件为真时重复某些表达式,请使用while ... end构造。

julia> x = 0
0
julia> while x < 4
           println(x)
           global x += 1
       end

0
1
2
3

如果在函数外部工作,则需要 global 声明 x,然后才能更改其值。在函数内部,不需要global

如果希望在语句之后(而不是在语句之前)测试条件,生成“do...until”的形式,请使用以下结构:

while true
   println(x)
   x += 1
   x >= 4 && break
end

0
1
2
3

这里我们使用的是布尔转换表达式而不是if ... end语句。

使用Julia的宏,您可以创建自己的控制结构。请参见 Metaprogramming 元编程。

异常

编辑

如果要编写检查错误并妥善处理这些错误的代码,请使用try ... catch结构。

使用catch短语,您可以处理代码中出现的问题,这可能会使程序继续运行,而不是陷入停顿。


在下一个示例中,我们的代码试图直接更改字符串的第一个字符(这是不允许的,因为Julia中的字符串不能就地修改):

julia> s = "string";
julia> try
          s[1] = "p"
       catch e
          println("caught an error: $e")
          println("but we can continue with execution...")
       end

 caught an error: MethodError(setindex!,("string","p",1)) but we can continue with execution...

error()函数使用给定的消息引发错误异常。

Do block

编辑

最后,让我们看一看do代码块,这是另一种语法形式,就像列表推导一样,乍看起来有点倒退(也就是说,从末尾开始,从开头开始或许可以更好地理解它)。

还记得 前面的 find()示例吗?

julia> smallprimes = [1,2,3,5,7,11,13,17,19,23];
julia> findall(x -> isequal(13, x), smallprimes)
1-element Array{Int64,1}:
7

anonymous function 匿名函数 (x -> isequal(13, x)find()的第一个参数,它对第二个参数进行操作。但是有了do代码块,你就可以把函数拿出来放在do之间……

julia> findall(smallprimes) do x
         isequal(x, 13) 
      end
1-element Array{Int64,1}:
7

您只需丢掉箭头、更改顺序,将find()函数及其目标参数放在第一位,然后在 do之后添加匿名函数的参数和主体。


这样做的目的是在表单末尾的多行上编写一个较长的匿名函数,而不是将其作为第一个参数插入。


« Introducing Julia
Controlling the flow
»
Types Functions