【问题标题】:Reorder numpy array to new bitlength elements without loop将 numpy 数组重新排序为没有循环的新位长元素
【发布时间】:2021-11-17 18:56:05
【问题描述】:

如果我有一个 numpy 数组,其中每个元素代表例如一个 9 位整数,是否有一种简单的方法(可能没有循环)对其重新排序,结果数组元素每个都表示一个 8 位整数,在前一个元素的末尾添加了“丢失的位”在下一个元素的开头? 例如获取以下内容

np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100])  # initial array in binarys
# convert to
np.array([0b10011100, 0b01001011, 0b00110011, 0b10011001, 0b01000000])  # resulting array

我希望我想要存档的内容是可以理解的。 附加信息,我不知道这是否有任何区别: 我所有的 9 位数字都以 msb beeing 1 开头(它们大于 255),最后两位总是 0,就像上面的例子一样。 我要处理的数组更大,包含数千个元素。

提前感谢您的帮助!

编辑:

我目前的(复杂的)解决方案如下:

import numpy as np
def get_bits(data, offset, leng):
    data = (data % (1 << (offset + leng))) >> offset
    return data

data1 = np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100])
i = 1
part1 = []
part2 = []
for el in data1:
    if i == 1:
        part2.append(0)
    part1.append(get_bits(el, i, 8))
    part2.append(get_bits(el, 0, i)<<(8-i))
    if i == 8:
        i = 1
        part1.append(0)
    else:
        i += 1
if i != 1:
    part1.append(0)
res = np.array(part1) + np.array(part2)

【问题讨论】:

  • 你目前是怎么做的?
  • @Copperfield 我已经添加了我当前的代码!
  • 很好,我可以尝试用循环的东西或调整到任何尺寸的东西来改进我的,但看起来 Mad Physicist 为您提供了更好的解决方案
  • @Phaneroptera。您也享有投票的声誉。
  • @Copperfield 是的,我认为到目前为止,Mad Physicist 的表现优于我们的解决方案。但是感谢您的帮助!

标签: python arrays numpy bit-manipulation numpy-ndarray


【解决方案1】:

np.packbitsnp.unpackbits 效率低下一直困扰着我,所以我想出了一个有趣的答案。

一般的想法是像任何重采样器一样工作:创建一个输出数组,并找出每个输出片段在输入中的来源。你有 N 每个 9 位的元素,所以输出是:

data = np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100])
result = np.empty(np.ceil(data.size * 9 / 8).astype(int), dtype=np.uint8)

相对于相应的八个输入字节,每九个输出字节具有以下模式。我使用{...} 表示每个输入整数中的(包括)位:

result[0] =              data[0]{8:1}
result[1] = data[0]{0:0} data[1]{8:2}
result[2] = data[1]{1:0} data[2]{8:3}
result[3] = data[2]{2:0} data[3]{8:4}
result[4] = data[3]{3:0} data[4]{8:5}
result[5] = data[4]{4:0} data[5]{8:6}
result[6] = data[5]{5:0} data[6]{8:7}
result[7] = data[6]{6:0} data[7]{8:8}
result[8] = data[7]{7:0}

result 的索引(称为i)实际上是以 9 为模给出的。因此,数据的索引偏移了8 * (i // 9)。字节的较低部分由data[...] &gt;&gt; (i + 1) 给出。上部由data[...] &amp; ((1 &lt;&lt; i) - 1) 给出,左移8 - i 位。

这使得提出矢量化解决方案变得非常容易:

i = np.arange(result.size)
j = i[:-1]
result[i] = (data[8 * (i // 9) + (i % 9) - 1] & ((1 << i % 9) - 1)) << (8 - i % 9)
result[j] |= (data[8 * (j // 9) + (j % 9)] >> (j % 9 + 1)).astype(np.uint8)

您需要剪裁低部分的索引,因为它可能会超出范围。您不需要剪裁高部分,因为 -1 是一个完全有效的索引,并且您不关心它访问哪个元素。当然 numpy 不会让你 OR 或将 int 元素添加到 uint8 数组,所以你必须强制转换。

>>> [bin(x) for x in result]
['0b10011100', '0b1001011', '0b110011', '0b10011001', '0b1000000']

这个解决方案应该可以扩展到任何大小的数组,我编写它是为了让你可以计算出不同的班次组合,而不仅仅是 9 到 8 班。

【讨论】:

  • 除了蜜蜂更快,这个解决方案也更好,因为它似乎更兼容!
【解决方案2】:

您可以使用np.unpackbitsnp.packbits 分两步完成。首先把你的数组变成一个little-endian列向量:

>>> z = np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100], dtype='<u2').reshape(-1, 1)
>>> z.view(np.uint8)
array([[ 56,   1],
       [ 44,   1],
       [156,   1],
       [148,   1]], dtype=uint8)

您可以通过解包直接将其转换为位数组。事实上,在某个时候 (PR #10855),我添加了 count 参数来为您去除高零点:

>>> np.unpackbits(z.view(np.uint8), axis=1, bitorder='l', count=9)
array([[0, 0, 0, 1, 1, 1, 0, 0, 1],
       [0, 0, 1, 1, 0, 1, 0, 0, 1],
       [0, 0, 1, 1, 1, 0, 0, 1, 1],
       [0, 0, 1, 0, 1, 0, 0, 1, 1]], dtype=uint8)

现在您可以重新打包反转的 raveled 数组:

>>> u = np.unpackbits(z.view(np.uint8), axis=1, bitorder='l', count=9)[:, ::-1].ravel()
>>> result = np.packbits(u)
>>> result.dtype
dtype('uint8')
>>> [bin(x) for x in result]
['0b10011100', '0b1001011', '0b110011', '0b10011001', '0b1000000']

如果您的机器是原生小端(例如,大多数 intel 架构),您可以在单行中执行此操作:

z = np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100])
result = np.packbits(np.unpackbits(z.view(np.uint8), axis=1, bitorder='l', count=9)[:, ::-1].ravel())

否则,您可以通过 z.byteswap().view(np.uint8) 获得正确的起始顺序(我想还是一个班轮)。

【讨论】:

  • 成功了,非常感谢!
【解决方案3】:

我想我理解了您想要的大部分内容,并且鉴于您可以使用 numpy 数组进行位操作,在这种情况下,如果使用两个数组(或者如果它是一个数组与一个数字),那么你需要构造适当的数组来做这件事,所以像这样

>>> import numpy as np
>>> x = np.array([0b100111000, 0b100101100, 0b110011100, 0b110010100])
>>> goal=np.array([0b10011100, 0b01001011, 0b00110011, 0b10011001, 0b01000000])
>>> x
array([312, 300, 412, 404])
>>> goal
array([156,  75,  51, 153,  64])
>>> shift1 = np.array(range(1,1+len(x)))
>>> shift1
array([1, 2, 3, 4])
>>> mask1  = np.array([2**n -1 for n in range(1,1+len(x))])
>>> mask1
array([ 1,  3,  7, 15])
>>> res=((x>>shift1)|((x&mask1)<<(9-shift1)))&0b11111111
>>> res
array([156,  75,  51, 153], dtype=int32)
>>> goal
array([156,  75,  51, 153,  64])
>>> 

我不明白为什么你的目标数组有一个额外的元素,但是上面的操作给了其他的数字,并且增加了一个并不复杂,所以需要调整。

现在解释((x&gt;&gt;shift1)|((x&amp;mask1)&lt;&lt;(9-shift1)))&amp;0b11111111

首先我注意到你按元素做了一个更大的转变,这很简单

>>> x>>shift1
array([156,  75,  51,  25], dtype=int32)
>>> 
>>> list(map(bin,x>>shift1))
['0b10011100', '0b1001011', '0b110011', '0b11001']
>>> 

我们还想捕捉因移位而丢失的位,通过 以及适当的掩码我们得到那些

>>> x&mask1
array([0, 0, 4, 4], dtype=int32)
>>> list(map(bin,mask1))
['0b1', '0b11', '0b111', '0b1111']
>>> list(map(bin,x&mask1))
['0b0', '0b0', '0b100', '0b100']
>>> 

然后我们将结果右移互补量

>>> 9-shift1
array([8, 7, 6, 5])
>>> ((x&mask1)<<(9-shift1))
array([  0,   0, 256, 128], dtype=int32)
>>> list(map(bin,_))
['0b0', '0b0', '0b100000000', '0b10000000']
>>> 

然后我们一起

>>> (x>>shift1) | ((x&mask1)<<(9-shift1))
array([156,  75, 307, 153], dtype=int32)
>>> list(map(bin,_))
['0b10011100', '0b1001011', '0b100110011', '0b10011001']
>>> 

最后我们用 0b11111111 只保留我们想要的 8 位


另外,您提到最后 2 位始终为零,那么更简单的解决方案是简单地将其移位 2,并恢复原来的只需移回另一个方向

>>> x
array([312, 300, 412, 404])
>>> y = x>>2
>>> y
array([ 78,  75, 103, 101], dtype=int32)
>>> y<<2
array([312, 300, 412, 404], dtype=int32)
>>>     

【讨论】:

  • 非常感谢您的回答,我需要一些时间来尝试一下。我发现它不适用于超过 8 个元素。实际上我有一个更大的数组要处理,但我没有在问题中提到这一点是我的坏事。问题是结果数组与初始数组的差异,因为初始数组的每 8 个元素结果数组需要一个额外的元素。因此,我认为不可能使用您建议的算法实现通用解决方案。无论如何,这个想法很棒!
  • 您是来自完全通用解决方案的几个模运算符。我发布了第二个答案来证明这一点。
猜你喜欢
  • 2023-03-25
  • 1970-01-01
  • 2022-12-05
  • 1970-01-01
  • 1970-01-01
  • 2017-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多