在 Vectors概念中,我們說過「陣列是 Julia 語言的核心」,並提到向量是一維的。
既然如此,我們可以合理期待這個語言提供了許多靈活又強大的方式,讓我們對向量_做各種事_,無論那是什麼意思。
有些非常簡單的函式接受向量輸入,並回傳純量輸出。
v = [2, 3, 4]
length(v) # => 3
sum(v) # => 9
假設你有一個數值向量,想從每個值減去 0.5。
julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
1.2
1.5
1.7
julia> v - 0.5
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)
這樣行不通,那改成減去另一個向量呢?
julia> v - [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
成功了,但當向量越來越長,這種做法既麻煩又耗記憶體。
幸運的是,Julia 有個「魔法」點號可以非常簡單地解決這個問題:你只需要v .- 0.5。
下一節會說明原因。
所以,v - 0.5會失敗,但v .- 0.5卻能成功,我們得理解這個點號到底做了什麼。
它做了兩件事,兩者結合起來才能得到想要的結果。
首先,在任何中置運算子前面加上點號,代表「把這個運算分別套用到每個元素上」。
同樣地,在函式名稱_之後_加上點號會將它「向量化」,即使這個函式原本是為純量輸入而寫的。
julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
1.0
2.0
3.0
我們在前一個例子看到,可以對長度相同的向量做減法,但請理解,.-透過清楚表達逐元素的意圖,是比-_更安全_的運算子。
julia> v .- [0.5, 0.5, 0.5]
3-element Vector{Float64}:
0.7
1.0
1.2
那長度不同的向量呢?
julia> v .- [0.5, 0.5]
ERROR: DimensionMismatch: arrays could not be broadcast to a common size
julia> v .- [0.5,]
3-element Vector{Float64}:
0.7
1.0
1.2
一般來說,長度不同會造成錯誤,_除非_其中一個長度為 1(技術上稱為「單一元素」維度)。
像[0.5,]或單純的0.5這類單一元素,會透過重複自動擴展到所需的長度。
這正是broadcasting的核心。
透過索引編號選取向量元素,在先前的概念中已經討論過。
a = collect('A':'Z') # => 26-element Vector{Char}
# index with an integer
a[2] # => 'B'
# index with a range
a[12:2:18] # => ['L', 'N', 'P, 'R']
# index with another vector
a[ [1, 3, 5] ] # => ['A', 'C', 'E']
也可以選取滿足某個邏輯運算式(技術上稱為「述詞」)的元素。 這通常需要用到廣播。
julia> a[a .< 'D']
3-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
運算式更複雜時,點號往往會越長越多(不過它們很小,容易輸入)。
julia> a[a .< 'D' .|| a .> 'W']
6-element Vector{Char}:
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)
提醒一下,這裡的「向量」其實可以是任何合適的有序可迭代物件,例如範圍:
julia> n = 3:10
3:10
julia> n[isodd.(n)]
4-element Vector{Int64}:
3
5
7
9
你是個熱衷的賞鳥者,會持續記錄過去七天內有多少鳥兒造訪你的花園。
你有六項任務,全都和造訪你花園的鳥兒數量有關。
實作today()函式,回傳今天有多少鳥兒造訪你的花園。
鳥兒的數量依日期排序,第一個元素是最早那天的數量,最後一個元素則是今天的數量。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> today(birds_per_day)
1
實作increment_todays_count()函式,將今天的數量加一:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> increment_todays_count(birds_per_day)
[2, 5, 0, 7, 4, 2]
實作has_day_without_birds()函式,如果有任何一天沒有鳥兒造訪花園,就回傳true;否則回傳false:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> has_day_without_birds(birds_per_day)
true
實作count_for_first_days()函式,回傳從這週開始以來造訪你花園的鳥兒數量,但把計算範圍限制在從這週開始算起的指定天數內。
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> count_for_first_days(birds_per_day, 4)
14
有些日子比其他日子更熱鬧。
只要一天有 5 隻或更多鳥兒造訪你的花園,就算是忙碌的一天。
實作busy_days()函式,回傳忙碌的日子有幾天:
julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> busy_days(birds_per_day)
2
你決定擴充你的紀錄,開始記錄好幾週的數量。 每一組數量都依星期幾排列,從星期一作為第一筆到星期日作為最後一筆。
實作average_per_day()函式,回傳 2 週的平均值。
julia> week1 = [7, 2, 9, 1, 3, 0, 10]
julia> week2 = [2, 6, 4, 1, 3, 8, 9]
julia> average_per_day(week1, week2)
[4.5, 4.0, 6.5, 1.0, 4.0, 3.0, 9.5]