分配给所有索引总和为某个值的条目

时间:2019-05-09 18:56:02

标签: python numpy multidimensional-array vectorization

我有一个二进制数为X且形状为(2, 2, ..., 2)的数组,并希望将值1分配给所有索引总和为0模2的项,将值0分配给其余项。 / p>

例如,如果我们有X.shape = (2, 2, 2),那么我想将1分配给X[0, 0, 0], X[0, 1, 1], X[1, 0, 1], X[1, 1, 0],将0分配给其他4个条目。

最有效的方法是什么?我假设我应该使用np.bool数据类型创建此数组,因此解决方案应该牢记这一点。

1 个答案:

答案 0 :(得分:1)

这是一种直接方法,也是一种棘手的方法。棘手的人使用位打包并利用某些重复模式。对于较大的n,可以大大提高(>50 @ n=19)。

import functools as ft
import numpy as np

def direct(n):
    I = np.arange(2, dtype='u1')
    return ft.reduce(np.bitwise_xor, np.ix_(I[::-1], *(n-1)*(I,)))

def smartish(n):
    assert n >= 6
    b = np.empty(1<<(n-3), 'u1')
    b[[0, 3, 5, 6]] = 0b10010110
    b[[1, 2, 4, 7]] = 0b01101001
    i = b.view('u8')
    jp = 1
    for j in range(0, n-7, 2):
        i[3*jp:4*jp] = i[:jp]
        i[jp:3*jp].reshape(2, -1)[...] = 0xffff_ffff_ffff_ffff ^ i[:jp]
        jp *= 4
    if n & 1:
        i[jp:] = 0xffff_ffff_ffff_ffff ^ i[:jp]
    return np.unpackbits(b).reshape(n*(2,))

from timeit import timeit

assert np.all(smartish(19) == direct(19))

print(f"direct   {timeit(lambda: direct(19), number=100)*10:.3f} ms")
print(f"smartish {timeit(lambda: smartish(19), number=100)*10:.3f} ms")

2^19框上运行的示例:

direct   5.408 ms
smartish 0.079 ms

请注意,这些返回uint8数组,例如:

>>> direct(3)
array([[[1, 0],
        [0, 1]],

       [[0, 1],
        [1, 0]]], dtype=uint8)

但是这些可以以几乎零的成本播送到bool上:

>>> direct(3).view('?')
array([[[ True, False],
        [False,  True]],

       [[False,  True],
        [ True, False]]])

说明者:

直接方法:检查位奇偶校验的一种直接方法是将位xor在一起。我们需要以“减少”的方式执行此操作,即必须对前两个操作数,然后对结果和第三个操作数,然后对那个结果和第四个操作数应用二进制操作xor向前。 functools.reduce就是这样做的。

此外,我们不想只在2^n网格的每个点上执行一次。 numpy的实现方法是开放式网格。这些可以使用np.ix_从一维轴生成,或者在简单情况下可以使用np.ogrid生成。请注意,我们将第一个轴翻转以说明我们想要反转奇偶校验的事实。

聪明的方法。我们做了两个主要的优化。 1)xor是按位运算,这意味着如果我们将位打包成64位uint,它将免费执行“ 64路并行计算”。 2)如果我们展平2 ^ n超立方体,则线性排列中的位置n对应于超立方体中的单元格(bit1,bit2,bit3,...),其中bit1,bit2等是...的二进制表示(前导零) 。现在请注意,如果我们已经计算出位置0 .. 0b11..11 = 2 ^ k-1的奇偶校验,那么我们可以通过简单地复制和反转来获得2 ^ k..2 ^(k + 1)-1的奇偶校验已经计算的奇偶校验。例如k = 2:

0b000, 0b001, 0b010, 0b011 would be what we have and
0b100, 0b101, 0b110, 0b111 would be what we need to compute
  ^      ^      ^      ^   

由于这两个序列仅在标记的位上不同,因此很明显,它们的交叉数字总和确实相差1,并且奇偶校验是倒置的。

作为练习,可以用类似的方式来说明接下来的2 ^ k个条目和之后的2 ^ k个条目。