Complex numbers并不复杂。
它只是需要一个不那么吓人的名字。
它们非常有用,尤其在工程和科学领域,因此 R 把复数作为一种标准的原子类型,与整数、双精度浮点数、逻辑值、字符和 raw 并列。
在 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