4

Python 系列:衔尾蛇一样的取模

 1 year ago
source link: https://zhajiman.github.io/post/python_modulo/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Python 系列:衔尾蛇一样的取模

 2022-10-24

 1207 words

 python 

 4 views

Python 的取模运算 r = m % n 相当于

# 或q = math.floor(m / n)
q = m // n
r = m - q * n

即取模的结果是被除数减去地板除的商和除数的乘积,这一规则对正数、负数乃至浮点数皆适用。

n 为正数时。显然任意实数 x 可以表示为 x = r + k * n,其中 0 <= r < nk 是某个整数。那么有

x // n = floor(r/n + k) = k
x % n = x - x // n = r

x % n 的结果总是一个大小在 [0, n) 之间的实数 r。当 n = 10 时,以 x = 12x = -12 为例:

number

如果以 n 为一个周期,那么 x = 12 就相当于往右一个周期再走 2 格,x % n 会消去这个周期,剩下不满一个周期的 2;x = -12 相当于往左两个周期后再往右走 8 格,x % n 会消去这两个周期,剩下不满一个周期且为正数的 8。

再本质点说,取模运算就是在 [0, 10) 的窗口内进行“衔尾蛇”移动:

  • 12 向右超出窗口两格, 12 % 10 = 2,即右边出两格那就左边进两格。
  • -12 向左超出窗口 12 格,-12 % n = 8,即左边出 12 格那就右边进 12 格,发现还是超出左边两格,再从右边进两格,最后距离零点 8 格。

下面介绍取模运算的两个应用。

地球的经度以本初子午线为起点,自西向东绕行一圈,经度的数值从 0° 增长到 360°。不过经度还可以大于 360°,表示绕行一圈以上,甚至还可以是负数,表示自东向西绕行。显然这跟取模运算的衔尾蛇特性完美契合,通过取模运算可以将 [0, 360) 范围外的经度变换回这个范围内:

import numpy as np

lon = np.arange(-360, 720 + 1, 180)
print(lon)
print(lon % 360)
[-360, -180,    0,  180,  360,  540,  720]
[   0,  180,    0,  180,    0,  180,    0]

另外一个常用的经度范围是 [-180, 180),即经度跨过太平洋上的对向子午线时经度会从正数跳变到负数。问题是如何将 [0, 360] 范围内的经度变换到 [-180, 180) 范围内。显然 [-180, 180) 是一个窗口,我们希望范围在 [180, 360] 的经度从窗口右边离开,再从窗口左边进入。但因为窗口范围不满足 [0, n) 的形式,所以不能直接取模,而是应该先向右偏移 180°,在正轴完成衔尾蛇移动后再偏移回负轴:

(lon + 180) % 360 - 180
lon

注意,这一算法中 180° 会被算到 -180°,360° 会被算到 0°:

lon = lon = np.arange(0, 360 + 1, 180)
print(lon)
print((lon + 180) % 360 - 180)
[    0,  180,  360]
[    0, -180,    0],

第二个应用是将月份换算成季节。气候学上春季指 3、4、5 月份,夏季指 6、7、8 月份,秋季指 9、10、11 月份,冬季指 12 月和来年 1、2 月。这里暂时不考虑冬季跨年的问题(可参考笔者的 Period 文章),只是将 [1, 12] 的月份映射到 [1, 4] 上,1、2、3、4 分别表示春夏秋冬。

首先可以想到,地板除能将 12 个月等分为 4 组:

month = np.arange(1, 13)
print(month)
print((month - 1) // 3 + 1)
[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]
[ 1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4,  4]

可惜春天是在 1、2、3 月的基础上向右偏移两个月;冬天是在 10、11、12 月的基础上向右偏移两个月,超出 12 月的部分从左边重新进入(即 1、2 月)。那么可以考虑通过取模把月份向左“旋转”两格,让春天排在前三格的位置,冬天排在最后三格的位置,这样就能应用地板除做分组了:

(month - 3) % 12 // 3 + 1
season

当然,这两个问题都可以用更简单的方式来解决:经度可以用 np.where(lon > 180, 360 - lon, lon) 转换,季节可以用 if 判断或字典来做映射。但取模运算能将你的代码精简至一行,同时方便迷惑其它读者(大雾)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK