Complex numbersは複雑ではありません。ただ、もっとおだやかな名前が必要なだけです。
複素数は、とくに工学や科学の分野でとても役に立つため、Rではinteger、double、logical、character、rawと並ぶ標準のアトミック型として用意されています。
Rにおけるcomplexは、1つの値です。ただし、使うときには2つの数値のペアとして考えることができます。これらは、残念な歴史的な事情から「実部」と「虚部」と呼ばれます。ここでも、奇妙な名前ではなく、その根底にあるシンプルさに目を向けるのがよいでしょう。
2つの実数から複素数を作るには、虚部に接尾辞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
なぜjではなくiなのか、不思議に思うかもしれません。
多くのエンジニアは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
どちらの部分も0になり得るので、数学者はその数を「完全に実」あるいは「完全に虚」と呼ぶことがあります。しかし、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
あなたは、すでに飽和状態にある市場で他社との差別化を図ろうとしている新しいタクシーサービスに、採用されたばかりです。 これを実現するため、CEOはマンハッタンで超高級なサービスを提供することを決めました。すべての交差点に係員を配置し、乗車の前後で乗客をサポートします。 そこで、このサービスの運営を支えるコードを書く必要があります。
CEOは、マンハッタンの街路が主に格子状に区画されていることから、複素数が通りの交差点を表すのに便利だと判断しました。 ここでは、正と負の実数がそれぞれ東と西を、正と負の虚数がそれぞれ北と南を表します。
Rで複素数をインスタンス化するのは少し、ええと……独特なので、単純な複素数を扱う関数を実装するよう指示されました。
整数aとbを入力として受け取るsimplex(a, b)関数を実装します。
複素数a+biを返します。
simplex(1, 1)
#> 1+1i
simplex(-3, 4)
#> -3+4i
乗客が目的地の交差点を伝えると、ドライバーはそこへの行き方の指示を受け取ります。 この指示は複素数の形で渡され、実部が南北にいくつブロック進むかを、虚部が東西の距離を表します。 標準的な方位磁針に合わせるため、プロジェクトマネージャーは北を正の虚数方向、南を負の虚数方向とすることにしました。 同様に、東は正の実数方向、西は負の実数方向です。
出発地点startの交差点座標と、目的地endの座標を入力として受け取るdriver_directions(start, end)関数を実装します。
ドライバーが東西+南北にいくつブロック進む必要があるかを表す複素数を返します。
driver_directions(0+0i, 7+5i)
#> 7+5i
driver_directions(-2+3i, 4-5i)
#> 6-8i
乗客は、タクシーが移動しなければならない距離に応じた料金を支払います。 マンハッタンの街路は格子状に区画されているため、タクシーはこの経路に沿ってしか移動できません。 この構造から、格子線に沿うように制約された距離の測り方が生まれます。 この距離は、通過した線の数か、到達した交差点の数(つまり、出発地点の交差点は含みません)を合計することで求められます。 この測り方は一般にマンハッタン距離、またはL1ノルムと呼ばれます。
startとendの交差点を入力として受け取るmanhattan(start, end)関数を実装します。
startとendの交差点間のマンハッタン距離を返します。
manhattan(0+0i, 3+4i)
#> 7
manhattan(-2-3i, -1+1i)
#> 5
CEOは、このサービスが超高級であるだけでなく環境にも優しいと強調することで、投資銀行を味方につけることに成功しました。訓練されたカラスを使い、交差点にいる係員の間でメッセージを運ぶ(係員は、パーソナライズされた体験のために乗客に関する情報を伝えます)からです。 投資マネーがどんどん流れ込んでいるためか、投資銀行家たちは誰も、これがなぜ環境に優しいのか、そもそもどんなCEOが暇な時間にカラスの調教を楽しむのか、きちんと調べようとはしなかったようです。
このサービスも、移動した距離に応じて料金が発生します。 しかし、カラスは飛べるので、道路の格子網に従う必要はなく、最短経路を進むことができます。 もちろん、巨大な高層ビルがある場所ではそうはいかないかもしれません。しかしCEOは、このサービスをカンザス州マンハッタン(別名「The Little Apple」)に導入しようとしています。ここならカラスは低層の建物を楽に飛び越えられます。 カンザス州マンハッタンでこうしたサービスにどれだけの需要があるのか、そして頭上を飛び交う大量の鳥を地元の歩行者がどう感じるかは、また別の話です。
startとendの交差点を入力として受け取るas_crow_flies(start, end)関数を実装します。
2つの交差点間の直線距離を返します。
as_crow_flies(0+0i, 3+4i)
#> 5
CEOは、カラスにドライバーと似た種類の方向指示に従うよう訓練しました。 しかしCEOは南半球を愛しており、カラスには、プロジェクトマネージャーがシステムの他の部分で使っているものとは別の方位磁針で訓練しました。 CEOはなかなかの変わり者であることが分かってきました。 CEOの方位磁針では、北が負の虚数方向、南が正の虚数方向です。 これはドライバーの南北の方向とは逆で、東西は同じままです。
startとendの交差点を入力として受け取るcrows_directions(start, end)関数を実装します。
ドライバーの方向指示に相当する複素数を、南北を反転して返します。
driver_directions(0+0i, 7+5i)
#> 7-5i
driver_directions(-2+3i, 4-5i)
#> 6+8i