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