您可以使用shape(3, -1) 的连续数组视图,查找唯一出现的位置并在这些位置替换它们:
def view_ascontiguous(a): # a is array
a = np.ascontiguousarray(a)
void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[1]))
return a.view(void_dt).ravel()
def replace(x, args, subs, viewer):
u, inv = np.unique(viewer(x), return_inverse=True)
idx = np.searchsorted(viewer(args), u)
return subs[idx][inv]
>>> replace(x=np.array([1, 0, 1, 0, 0, 1, 1, 0, 1]).reshape(-1, 3),
args=np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]),
subs=np.array([ 5, 57, 58, 44, 67, 17, 77, 1]),
viewer=view_ascontiguous)
array([17, 57, 17])
注意这里idx表示Power Set{0, 1}^N中长度为N的唯一contiguous块的位置。
如果viewer(args) 将args 映射到np.searchsorted 方法内的[0, 1, 2, 3, ...],则将其替换为np.arange(len(args)) 有助于提高性能。
这个算法也可以用于更一般的问题:
您将获得 dtype=np.uint8 的数组,其中 M*N 的值为 0 和 1。您还获得了 Power Set [0, 1]^N(所有可能的长度为 0 和 1 的 N 的块)和一些标量值之间的映射.按照以下步骤查找 M 值的数组:
- 将您分配到
M 长度为N 的连续块中的拆分数组
- 使用给定的映射将每个块替换为标量值
现在,有趣的部分:您可以使用自己的viewer。需要将您传入 args 的数组映射到任何类型的升序索引,如下所示:
viewer=lambda arr: np.ravel_multi_index(arr.T, (2,2,2)) #0, 1, 2, 3, 4, 5, 6, 7
viewer=lambda arr: np.sum(arr * [4, 2, 1], axis=1) #0, 1, 2, 3, 4, 5, 6, 7
viewer=lambda arr: np.dot(arr, [4, 2, 1]) #0, 1, 2, 3, 4, 5, 6, 7
或者更有趣:
viewer=lambda arr: 2*np.dot(arr, [4, 2, 1]) + 1 #1, 3, 5, 7, 9, 11, 13, 15
viewer=lambda arr: np.vectorize(chr)(97+np.dot(arr, [4, 2, 1])) #a b c d e f g h
因为你也可以映射
[[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]
到任何你能想到的升序,比如[1, 3, 5, 7, 9, 11, 13, 15] 或['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
结果还是一样。
补充说明
感谢@MadPhysicist,
np.packbits(example.reshape(-1, N), axis=1, bitorder='little').ravel()
也能做到这一点。它假装是最快的解决方案,因为np.packbits 在numpy 中得到了很好的优化。