賞鳥者

賞鳥者

學習練習

簡介

在 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卻能成功,我們得理解這個點號到底做了什麼。

它做了兩件事,兩者結合起來才能得到想要的結果。

1. 逐元素套用

首先,在任何中置運算子前面加上點號,代表「把這個運算分別套用到每個元素上」。

同樣地,在函式名稱_之後_加上點號會將它「向量化」,即使這個函式原本是為純量輸入而寫的。

julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

2. 單一元素擴展

我們在前一個例子看到,可以對長度相同的向量做減法,但請理解,.-透過清楚表達逐元素的意圖,是比-_更安全_的運算子。

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

說明

你是個熱衷的賞鳥者,會持續記錄過去七天內有多少鳥兒造訪你的花園。

你有六項任務,全都和造訪你花園的鳥兒數量有關。

1. 查看今天有多少鳥兒造訪

實作today()函式,回傳今天有多少鳥兒造訪你的花園。 鳥兒的數量依日期排序,第一個元素是最早那天的數量,最後一個元素則是今天的數量。

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> today(birds_per_day)
1

2. 增加今天的數量

實作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]

3. 檢查是否有完全沒有鳥兒造訪的日子

實作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

4. 計算前幾天造訪鳥兒的數量

實作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. 計算忙碌的日子有幾天

有些日子比其他日子更熱鬧。 只要一天有 5 隻或更多鳥兒造訪你的花園,就算是忙碌的一天。 實作busy_days()函式,回傳忙碌的日子有幾天:

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> busy_days(birds_per_day)
2

6. 計算一週中各天的平均值

你決定擴充你的紀錄,開始記錄好幾週的數量。 每一組數量都依星期幾排列,從星期一作為第一筆到星期日作為最後一筆。

實作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]
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Julia Exercism

準備好開始 賞鳥者 了嗎?

註冊 Exercism,透過 35 個概念128 個練習 和真人引導來學習並精通 Julia,全部免費。