将 numpy 数组重新排序为没有循环的新位长元素
Reorder numpy array to new bitlength elements without loop
如果我有一个 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)
我想我理解了你想要的大部分内容,并且鉴于你可以对 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>>shift1)|((x&mask1)<<(9-shift1)))&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)
>>>
您可以使用 np.unpackbits
and np.packbits
分两步完成。首先将您的数组转换为小端列向量:
>>> 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)
现在你可以重新打包反转的数组:
>>> 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']
如果您的机器是本地小端(例如,大多数英特尔架构),您可以在一行中执行此操作:
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)
获得正确的开始顺序(不过我想仍然是一个班轮)。
一直困扰我的是 np.packbits
和 np.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[...] >> (i + 1)
给出。上部由 data[...] & ((1 << 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 不会让您或将 int
元素添加到 uint8
数组,因此您必须强制转换。
>>> [bin(x) for x in result]
['0b10011100', '0b1001011', '0b110011', '0b10011001', '0b1000000']
这个解决方案应该可以扩展到任何大小的数组,我写它是为了让你可以计算出不同的轮班组合,而不仅仅是朝九晚八。
如果我有一个 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)
我想我理解了你想要的大部分内容,并且鉴于你可以对 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>>shift1)|((x&mask1)<<(9-shift1)))&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)
>>>
您可以使用 np.unpackbits
and np.packbits
分两步完成。首先将您的数组转换为小端列向量:
>>> 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)
现在你可以重新打包反转的数组:
>>> 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']
如果您的机器是本地小端(例如,大多数英特尔架构),您可以在一行中执行此操作:
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)
获得正确的开始顺序(不过我想仍然是一个班轮)。
一直困扰我的是 np.packbits
和 np.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[...] >> (i + 1)
给出。上部由 data[...] & ((1 << 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 不会让您或将 int
元素添加到 uint8
数组,因此您必须强制转换。
>>> [bin(x) for x in result]
['0b10011100', '0b1001011', '0b110011', '0b10011001', '0b1000000']
这个解决方案应该可以扩展到任何大小的数组,我写它是为了让你可以计算出不同的轮班组合,而不仅仅是朝九晚八。