Julia 簡介/控制流程
通常,Julia 程式的每一行都是依次執行的。有幾種方法可以控制和修改執行流程。這些對應於其他語言中使用的結構
- 三元和複合表示式
- 布林開關表示式
- if elseif else end — 條件執行
- for end — 迭代執行
- while end — 迭代條件執行
- try catch error throw 異常處理
- do 塊
通常,你想要在某個條件為真的情況下執行任務 A(或呼叫函式 A),或者在條件不為真的情況下執行任務 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 是逐個計算表示式的,因此可以輕鬆地安排表達式,使其僅在先前的條件為真或假時才被計算。
以下示例使用一個 Julia 函式,該函式根據數字是奇數還是偶數返回真或假:`isodd(n)`。
對於 `&&`,兩部分都必須為真,因此我們可以這樣寫
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!
如果第一個條件為真,則無需計算第二個表示式,因為我們已經獲得了“或”所需的唯一真值,並且它將返回真值。如果第一個條件為假,則計算第二個表示式,因為該表示式可能為真。
這種型別的計算也稱為“短路計算”。
對於更通用(也是更傳統)的條件執行方法,可以使用 `if`、`elseif` 和 `else`。如果你習慣於其他語言,別擔心空格、大括號、縮排、方括號、分號或任何類似的東西,但要記住用 `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
`elseif` 和 `else` 部分也是可選的
name = "Julia"
if name == "Julia"
println("I like Julia")
end
不要忘記 `end`!
那麼“switch”和“case”語句呢?不用擔心學習它們的語法,因為它們不存在!
還有一個 `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` ... `end` 結構允許你遍歷多種型別的物件,包括範圍、陣列、集合、字典和字串。
以下是簡單遍歷值範圍的標準語法
julia> for i in 0:10:100
println(i)
end
0
10
20
30
40
50
60
70
80
90
100
變數 `i` 依次獲取陣列中每個元素的值(該陣列由範圍物件構建)——這裡以 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` 在迴圈開始之前存在,則只能在迴圈中使用 `global` 更改它的值。
在 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。
這種行為是因為我們正在 REPL 中工作。通常,最好將程式碼放在函式內部,這樣你就不需要將從迴圈外部繼承的變數標記為全域性變數
function f()
counter = 0
for i in 1:10
counter += i
end
return counter
end
julia> f() 55
微調迴圈:Continue
[edit | edit source]有時,在特定迴圈中,您可能希望跳過到下一個值。您可以使用 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
推導式
[edit | edit source]這個奇怪的名字概念僅僅是生成和收集專案的一種方法。在數學領域,您會這樣說
"Let S be the set of all elements n where n is greater than or equal to 1 and less than or equal to 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] 結構被稱為 **陣列推導式** 或 **列表推導式** (“推導式”在“獲取所有內容”而不是“理解”的意義上)。外部括號收集由在 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
生成器表示式
[edit | edit source]與推導式類似,生成器表示式可用於從迭代變數中生成值,但與推導式不同,這些值是按需生成的。
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
列舉陣列
[edit | edit source]通常,您希望逐元素遍歷陣列,同時跟蹤每個元素的索引號。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)
在迴圈的每次迭代中,都會檢查陣列是否有可能的變化。
壓縮陣列
[edit | edit source]有時您希望同時遍歷兩個或多個數組,先取每個陣列的第一個元素,然後是第二個元素,依此類推。使用名稱恰當的 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)
可迭代物件
[edit | edit source]“for something in something” 結構對於所有可以迭代的內容都是一樣的:陣列、字典、字串、集合、範圍等等。在 Julia 中,這是一個通用原則:您可以建立“可迭代物件”的多種方式,該物件旨在用作迭代過程的一部分,該過程一次提供一個元素。
我們已經見過的最明顯的例子是範圍物件。當您將其輸入 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]
但對於任何大型事物,都會有數百或數千個排列。這就是迭代器物件不會同時生成迭代中的所有值的緣故:記憶體和效能。範圍物件佔用的空間並不大,即使遍歷它可能需要很長時間,具體取決於範圍的大小。如果您一次生成所有數字,而不是僅在需要時生成它們,那麼所有這些數字都必須儲存在某個地方,直到您需要它們……
Julia 為處理其他型別的資料提供了可迭代物件。例如,當您處理檔案時,可以將開啟的檔案視為可迭代物件
filehandle = "/Users/me/.julia/logs/repl_history.jl"
for line in eachline(filehandle)
println(length(line), line)
end
使用 eachindex()
[edit | edit source]遍歷陣列時的一種常見模式是對 i 的每個值執行某些任務,其中 i 是每個元素的索引號,而不是元素本身
for i in eachindex(A)
# do something with i or A[i]
end
這是慣用的 Julia 程式碼,在所有情況下都是正確的,並且在某些情況下(比接下來的程式碼)速度更快。一個不好的程式碼模式來完成相同的事情,在它起作用的情況下(並非總是如此),是
for i = 1:length(A)
# do something with i or A[i]
end
注意高階使用者
[edit | edit source]為了介紹的目的,假設陣列和矩陣的索引從 1 開始可能是可以的(對於完全通用的程式碼來說並非如此,即用於在註冊的包中引入)。但是,在 Julia 中,當然可以使用其他索引基 - 例如,OffsetArrays.jl 包允許您選擇任何起始索引。當您開始使用更高階的陣列索引型別時,最好閱讀 [1] 中的官方文件。
更多迭代器
[edit | edit source]有一個名為 IterTools.jl 的 Julia 包,提供了一些高階迭代器函式。
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]
巢狀迴圈
[edit | edit source]如果您想將一個迴圈巢狀在另一個迴圈中,則不必重複 for 和 end 關鍵字。只需使用逗號即可
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 會從內部和外部迴圈中退出,但在長形式中,它只會從內部迴圈中退出。
最佳化巢狀迴圈
[edit | edit source]在 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` ... `end` 結構。
julia> x = 0
0
julia> while x < 4
println(x)
global x += 1
end
0
1
2
3
如果在函式之外工作,則在更改 `x` 的值之前需要 `global` 宣告。在函式內部,不需要 `global`。
如果希望在語句之後而不是之前測試條件,從而產生“do .. until”形式,請使用以下結構
while true
println(x)
x += 1
x >= 4 && break
end
0
1
2
3
這裡我們使用布林開關而不是 `if` ... `end` 語句。
這是一個 `while` 迴圈的基本模板,它將重複執行函式 `find_value`,直到它返回的值不大於 0。
function find_value(n) # find next value if current value is n
return n - 0.5
end
function main(start=10)
attempts = 0
value = start # starting value
while value > 0.0
value = find_value(value) # next value given this value
attempts += 1
println("value: $value after $attempts attempts" )
end
return value, attempts
end
final_value, number_of_attempts = main(0)
println("The final value was $final_value, and it took $number_of_attempts attempts.")例如,透過少量更改,此程式碼探索了著名的 Collatz 猜想
function find_value(n)
ifelse(iseven(n), n ÷ 2, 3n + 1) # Collatz calculation
end
function main(start=10)
attempts = 0
value = start # starting value
while value > 1 # while greater than 1
value = find_value(value)
attempts += 1
println("value: $value after $attempts attempts" )
end
return value, attempts
end
final_value, number_of_attempts = main(27)
println("The final value was $final_value, and it took $number_of_attempts attempts.")`main(12)` 需要 9 次嘗試,而 `main(27)` 需要 111 次嘗試。
使用 Julia 的宏,可以建立自己的控制結構。請參閱 超程式設計。
如果希望編寫檢查錯誤並優雅地處理它們的程式碼,請使用 `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` 塊,它是另一種語法形式,與列表推導一樣,乍一看有點倒退(即,可能透過從末尾開始,逐步向前理解)。
還記得 之前 的 `find()` 示例嗎?
julia> smallprimes = [2,3,5,7,11,13,17,19,23];
julia> findall(x -> isequal(13, x), smallprimes)
1-element Array{Int64,1}:
6
匿名函式 ( `x -> isequal(13, x)` ) 是 `find()` 的第一個引數,它對第二個引數進行操作。但是使用 `do` 塊,可以將函式提升出來,並將其放在 `do ... end` 塊結構之間
julia> findall(smallprimes) do x
isequal(x, 13)
end
1-element Array{Int64,1}:
6
您只需要刪除箭頭,並更改順序,將 `find()` 函式及其目標引數放在前面,然後在 `do` 之後新增匿名函式的引數和主體。
這樣做的目的是,將較長的匿名函式寫在表單末尾的幾行中,比將其作為第一個引數插入更容易。