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
你剛被一家新的計程車服務公司錄取,這家公司想在早已飽和的市場中做出區隔。 為了達成這個目標,執行長決定在曼哈頓推出極致奢華的服務,在每個路口都安排服務人員,在乘客搭乘前後協助他們。 現在,你需要寫一些程式碼,來協助這項服務的營運。
你的執行長認為,既然曼哈頓的街道大致呈格狀,用複數來表示街道的交叉口會很方便。 其中正實數代表東方,負實數代表西方,正虛數代表北方,負虛數代表南方。
由於在 R 中建立複數的方式有點……嗯,與眾不同,所以你被指派實作一個簡單的複數函式。
實作simplex(a, b)函式,它接受整數a和b。
回傳複數a+bi。
simplex(1, 1)
#> 1+1i
simplex(-3, 4)
#> -3+4i
當乘客說出目的地的路口時,你的駕駛會取得前往該處的方向指引。 這些指引以複數的形式呈現,實部代表要往北或往南走幾個街區,虛部則代表東西向的距離。 為了對應標準的羅盤方位,你的專案經理決定把北方定在正虛數方向,南方定在負虛數方向。 同樣地,東方是正實數方向,西方是負實數方向。
實作driver_directions(start, end)函式,它接受起始路口的座標start以及目的地的座標end。
回傳一個複數,代表駕駛要往東/西和往北/南各走多少個街區。
driver_directions(0+0i, 7+5i)
#> 7+5i
driver_directions(-2+3i, 4-5i)
#> 6-8i
乘客需依計程車必須行駛的距離付費。 由於曼哈頓的街道呈格狀,計程車只能沿著這些路線行駛。 這樣的結構使得距離的計算必須沿著格線進行。 這個距離可以把走過的街道線數加總,或把抵達的路口數加總(也就是不含起點路口)得出。 這種度量方式通常稱為曼哈頓距離或 L1 範數。
實作manhattan(start, end)函式,它接受start與end兩個路口。
回傳start與end兩個路口之間的曼哈頓距離。
manhattan(0+0i, 3+4i)
#> 7
manhattan(-2-3i, -1+1i)
#> 5
執行長向投資銀行強調,這項服務不僅極致奢華,還很環保,因為它會利用訓練過的烏鴉,在各路口的服務人員之間傳遞訊息(由他們轉達乘客的資訊,以提供個人化的體驗),因此成功說服了投資銀行加入。 看來這些投資銀行家都沒費心去查清楚,這到底為什麼環保,甚至也沒去了解什麼樣的執行長會喜歡在空閒時間訓練烏鴉,因為投資資金已經源源不絕地湧入。
這項服務同樣依飛行距離收費。 不過,烏鴉會飛,不必沿著格狀的道路網移動,可以走最短路徑。 當然,如果有巨大的摩天大樓,情況可能就不是如此;但執行長把這項服務設在堪薩斯州的曼哈頓(又稱「小蘋果」),這裡的烏鴉可以輕鬆飛越低矮的建築物。 這類服務在堪薩斯州曼哈頓的潛在需求,以及當地行人對這些鳥兒在頭頂飛來飛去會有什麼感受,都是以後再說的事了。
實作as_crow_flies(start, end)函式,它接受start與end兩個路口。
回傳兩者之間的直線距離。
as_crow_flies(0+0i, 3+4i)
#> 5
你的執行長把烏鴉訓練成能聽懂與駕駛類似的方向指引。 不過,執行長熱愛南半球,所以訓練烏鴉時用的是與專案經理在系統其他部分使用的不同方位。 你的執行長原來是個相當古怪的人。 執行長的方位把北方定在負虛數方向,南方定在正虛數方向。 這與駕駛的南北方向正好相反,而東西方向則維持不變。
實作crows_directions(start, end)函式,它接受start與end兩個路口。
回傳與駕駛行進方向類似的複數,但南北方向對調。
driver_directions(0+0i, 7+5i)
#> 7-5i
driver_directions(-2+3i, 4-5i)
#> 6+8i