如何有效地将 X 模 Y 添加到 numpy 数组中的每个元素?

How can I efficiently add X modulo Y to every element in a numpy array?

假设我有一个非常大的 1 到 180 之间的值数组,这些值在 uint8 的数组中(可以高达 255)。我想为每个值添加 90(模 180):

original = np.array([1, 2, 3, 170, 171, 172], dtype=np.uint8)
modified = (original + 90) % 180

不幸的是,这会产生不正确的结果,因为较大的数字在添加 90 时会在第一步中溢出其 uint8 整数:170 + 90 = 260 大于 255。

# (170 + 90) % 180 is 80, not 4 :(
array([91, 92, 93,  4,  5,  6], dtype=uint8)

我在一个对性能非常敏感的环境中操作,我的输入列表非常大。因此,我想避免将此数组转换为更大的数据类型的代价,并且我想使用高效的操作(例如,避免遍历数组并单独处理每个值)。

我怎样才能做到这一点?

您可以简单地将数组转换为 np.unit16:

>>> ((original.astype(np.uint16) + 90) % 180).astype(np.uint8)
array([91, 92, 93, 80, 81, 82], dtype=uint8)

如果你只想使用 uint8 来实现这个,你可以只增加那些大于 255-90 的元素:

>>> modified = (original + 90) % 180
>>> modified[original >= 255-90] += 256-180
>>> modified
array([91, 92, 93, 80, 81, 82], dtype=uint8)
import numpy as np

分配一个大小为 (10, 10) 的随机 numpy 整数数组,其中元素的最大值可以为 255。

np_array = np.random.randint(255, size=(10, 10))

对每个元素加 90 并对其取模。

np_array = (np_array + 90) % 180

这是一个有一些数学知识但没有向上转换的 -

def add_with_modulus(original, addval, modulusval=180):
    v = original + addval
    v[modulusval-original<=addval] += 256-modulusval
    return v

用法:add_with_modulus(original, addval=90, modulusval=180)

一个非常简单的选择,因为你正在处理 uint8,就是简单地预先计算数组中每个可能值的结果并使用它:

import numpy as np
original = np.array([1, 2, 3, 170, 171, 172], dtype=np.uint8)
value_map = ((np.arange(256) + 90) % 180).astype(np.uint8)
modified = value_map[original]
print(modified)
# [91 92 93 80 81 82]

这样做的好处是它不会占用超过 256 个元素的任何额外内存 value_map,并且对于任何更大的数组,您也将节省大部分计算。

运行 对选角的时间基准:

import numpy as np

def add_val_mod_cast(a, val, mod):
    return ((a.astype(np.uint16) + val) % mod).astype(np.uint8)

def add_val_mod_map(a, val, mod):
    value_map = ((np.arange(256) + val) % mod).astype(np.uint8)
    return value_map[a]

np.random.seed(0)
a = np.random.randint(256, size=10_000_000).astype(np.uint8)
val = 90
mod = 180
print((add_val_mod_cast(a, val, mod) == add_val_mod_map(a, val, mod)).all())
# True
%timeit add_val_mod_cast(a, val, mod)
# 72.6 ms ± 2.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit add_val_mod_map(a, val, mod)
# 40.8 ms ± 606 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)