學習軌道
/
R
R
/
練習
/
烏鴉計程車
烏鴉計程車

烏鴉計程車

學習練習

簡介

Complex numbers 並不複雜。 它們只是需要一個不那麼嚇人的名字。

它們非常實用,尤其在工程與科學領域,因此 R 把複數列為標準的原子型別,與整數、雙精度浮點數、邏輯值、字元以及原始型別並列。

基礎

在 R 中,complex 數值是一個單一的值。 不過實際使用時,可以把它想成一對數字。 基於一些不幸的歷史因素,這兩部分被稱為「實部」和「虛部」。 同樣地,最好把重點放在它本質上的單純,而不是那些奇怪的名稱。

若要從兩個實數建立複數,只要在虛部加上後綴i就好。

2+3i
#> [1] 2+3i

typeof(2+3i)
#> [1] "complex"

若要從實數變數建立複數,上面的語法就行不通了。 寫成r + imi會讓解析器誤以為imi是(不存在的)變數名稱。

你可以把虛部乘上1i,或者使用[complex()][ref-complex]函式:

r <- 2
im <- 3

r + im * 1i
#> [1] 2+3i

# must include the `real` and `imaginary` keywords
complex(real = r, imaginary = im)
#> [1] 2+3i

你也許會想,為什麼是i而不是j?

大多數工程師偏好j。 大多數科學家和數學家偏好代表虛數的數學符號i,但這個符號和電機工程中用i代表電流的慣例衝突。

R 選擇了i,Python 選擇了j,而 Julia 則選擇im來迴避這場爭議。 在 [STEM][wiki-STEM] 程式語言之間切換時要小心。

若要個別取出複數的各個部分:

z <- 2+3i

Re(z)  # real part
#> [1] 2

Im(z)  # imaginary part
#> [1] 3

任一部分都可以是零,數學家可能會因而說這個數「完全是實數」或「完全是虛數」。 不過在 R 中,它仍然是一個複數。

1.2 + 0i
#> [1] 1.2+0i

typeof(1.2 + 0i)
#> [1] "complex"

2i
#> [1] 0+2i

typeof(2i)
#> [1] "complex"

你或許聽過「i(或j)是 -1 的平方根」。

目前為止,這句話只意味著虛部_依定義_滿足下列等式:

1i * 1i == -1  # more precisely, 1i * 1i == -1 + 0i
#> [1] TRUE

這是個簡單的想法,卻會帶來有趣的結果。

算術

大多數用於浮點數與整數的運算子也能用在複數上:

z1 = 1.5 + 2i
z2 = 2 + 1.5i

z1 + z2  # addition
#> [1] 3.5+3.5i

z1 * z2  # multiplication
#> [1] 0+6.25i

z1 / z2  # division
#> [1] 0.96+0.28i

z1^2  # exponentiation
#> [1] -1.75+6i

2^z1  # another exponentiation
#> [1] 0.518895+2.780422i

函式

大多數數學函式都能接受複數輸入。

除了Re()和Im()之外,還有幾個函式與複數特別相關。

  • Conj()只是把複數虛部的正負號反轉(從 + 變 -,或反之)。
    • 由於複數乘法的運作方式,這個函式比你想像的還好用。
  • abs(<complex number>)保證會回傳一個沒有虛部的實數(也就是它的「大小」),而Mod(<complex number>)是同義詞。
  • Arg(<complex number>)會回傳以弧度表示的相位角。
z1
#> [1] 1.5+2i

abs(z1)
#> [1] 2.5

Mod(z1)  # same as previous
#> [1] 2.5

Conj(z1)  # flip sign
#> [1] 1.5-2i

Arg(z1)
#> [1] 0.9272952

說明

你剛被一家新的計程車服務公司錄取,這家公司想在早已飽和的市場中做出區隔。 為了達成這個目標,執行長決定在曼哈頓推出極致奢華的服務,在每個路口都安排服務人員,在乘客搭乘前後協助他們。 現在,你需要寫一些程式碼,來協助這項服務的營運。

1. 簡單複數

你的執行長認為,既然曼哈頓的街道大致呈格狀,用複數來表示街道的交叉口會很方便。 其中正實數代表東方,負實數代表西方,正虛數代表北方,負虛數代表南方。

由於在 R 中建立複數的方式有點……嗯,與眾不同,所以你被指派實作一個簡單的複數函式。

實作simplex(a, b)函式,它接受整數a和b。 回傳複數a+bi。

simplex(1, 1)
#> 1+1i

simplex(-3, 4)
#> -3+4i

2. 取得駕駛的行進方向

當乘客說出目的地的路口時,你的駕駛會取得前往該處的方向指引。 這些指引以複數的形式呈現,實部代表要往北或往南走幾個街區,虛部則代表東西向的距離。 為了對應標準的羅盤方位,你的專案經理決定把北方定在正虛數方向,南方定在負虛數方向。 同樣地,東方是正實數方向,西方是負實數方向。

實作driver_directions(start, end)函式,它接受起始路口的座標start以及目的地的座標end。 回傳一個複數,代表駕駛要往東/西和往北/南各走多少個街區。

driver_directions(0+0i, 7+5i)
#> 7+5i

driver_directions(-2+3i, 4-5i)
#> 6-8i

3. 取得曼哈頓距離

乘客需依計程車必須行駛的距離付費。 由於曼哈頓的街道呈格狀,計程車只能沿著這些路線行駛。 這樣的結構使得距離的計算必須沿著格線進行。 這個距離可以把走過的街道線數加總,或把抵達的路口數加總(也就是不含起點路口)得出。 這種度量方式通常稱為曼哈頓距離或 L1 範數。

實作manhattan(start, end)函式,它接受start與end兩個路口。 回傳start與end兩個路口之間的曼哈頓距離。

manhattan(0+0i, 3+4i)
#> 7

manhattan(-2-3i, -1+1i)
#> 5

4. 取得「直線距離」

執行長向投資銀行強調,這項服務不僅極致奢華,還很環保,因為它會利用訓練過的烏鴉,在各路口的服務人員之間傳遞訊息(由他們轉達乘客的資訊,以提供個人化的體驗),因此成功說服了投資銀行加入。 看來這些投資銀行家都沒費心去查清楚,這到底為什麼環保,甚至也沒去了解什麼樣的執行長會喜歡在空閒時間訓練烏鴉,因為投資資金已經源源不絕地湧入。

這項服務同樣依飛行距離收費。 不過,烏鴉會飛,不必沿著格狀的道路網移動,可以走最短路徑。 當然,如果有巨大的摩天大樓,情況可能就不是如此;但執行長把這項服務設在堪薩斯州的曼哈頓(又稱「小蘋果」),這裡的烏鴉可以輕鬆飛越低矮的建築物。 這類服務在堪薩斯州曼哈頓的潛在需求,以及當地行人對這些鳥兒在頭頂飛來飛去會有什麼感受,都是以後再說的事了。

實作as_crow_flies(start, end)函式,它接受start與end兩個路口。 回傳兩者之間的直線距離。

as_crow_flies(0+0i, 3+4i)
#> 5

5. 取得烏鴉的行進方向

你的執行長把烏鴉訓練成能聽懂與駕駛類似的方向指引。 不過,執行長熱愛南半球,所以訓練烏鴉時用的是與專案經理在系統其他部分使用的不同方位。 你的執行長原來是個相當古怪的人。 執行長的方位把北方定在負虛數方向,南方定在正虛數方向。 這與駕駛的南北方向正好相反,而東西方向則維持不變。

實作crows_directions(start, end)函式,它接受start與end兩個路口。 回傳與駕駛行進方向類似的複數,但南北方向對調。

driver_directions(0+0i, 7+5i)
#> 7-5i

driver_directions(-2+3i, 4-5i)
#> 6+8i
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
R Exercism

準備好開始 烏鴉計程車 了嗎?

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