【问题标题】:How to sort rows of a binary-valued array as if they were long binary numbers?如何对二进制值数组的行进行排序,就好像它们是长二进制数一样?
【发布时间】:2018-04-07 17:45:27
【问题描述】:

有一个 2D numpy 数组,大约 500000 行,每行 512 个值:

[
  [1,0,1,...,0,0,1], # 512 1's or 0's
  [0,1,0,...,0,1,1],
  ...
  [0,0,1,...,1,0,1], # row number 500000
]

如何对行进行升序排序,就好像每一行都是一个长的 512 位整数?

[
  [0,0,1,...,1,0,1],
  [0,1,0,...,0,1,1],
  [1,0,1,...,0,0,1],
  ...
]

【问题讨论】:

  • 我不确定,但是“sorted”函数可以对作为字符数组的字符串进行排序,也许它适用于整数数组?
  • @Orionss 坏主意。不要在涉及 numpy 数组的地方引入 python 函数。

标签: python arrays sorting numpy


【解决方案1】:

除了转换为字符串,您还可以使用数据的void 视图(来自@Jaime here)和argsort

def sort_bin(b):
    b_view = np.ascontiguousarray(b).view(np.dtype((np.void, b.dtype.itemsize * b.shape[1])))
    return b[np.argsort(b_view.ravel())] #as per Divakar's suggestion

测试

np.random.seed(0)

b = np.random.randint(0, 2, (10,5))
print(b)
print(sort_bin(b))

[[0 1 1 0 1]
 [1 1 1 1 1]
 [1 0 0 1 0]
 ..., 
 [1 0 1 1 0]
 [0 1 0 1 1]
 [1 1 1 0 1]]
[[0 0 0 0 1]
 [0 1 0 1 1]
 [0 1 1 0 0]
 ..., 
 [1 1 1 0 1]
 [1 1 1 1 0]
 [1 1 1 1 1]]

由于b_view 只是b 的一个视图,因此应该更快且内存占用更少

t = np.random.randint(0,2,(2000,512))

%timeit sort_bin(t)
100 loops, best of 3: 3.09 ms per loop

%timeit np.array([[int(i) for i in r] for r in np.sort(np.apply_along_axis(lambda r: ''.join([str(c) for c in r]), 0, t))])
1 loop, best of 3: 3.29 s per loop

实际上快了大约 1000 倍

【讨论】:

  • 聪明人! b[np.argsort(b_view.ravel())] 的一些显着改进。
  • 谢谢 Divakar,我忘了.flatten() 复制了。
【解决方案2】:

您可以将它们以stable 的方式排序 512 次,从最右边的位开始。

  1. 按最后一位排序
  2. 按倒数第二位排序,稳定(不会弄乱先前排序的结果)
  3. ... ...
  4. 按第一位排序,稳定

一个较小的例子:假设您要按位对这三个 2 位数字进行排序:

11
01
00

在第一步中,您按正确的位排序,结果是:

00
11
01

现在您按第一位排序,在这种情况下,我们在该列中有两个 0s。如果您的排序算法不稳定,则允许将这些相等的项目以任何顺序放置在结果中,这可能会导致 01 出现在我们不希望的 00 之前,因此我们使用稳定排序,保持相等项目的相对顺序,对于第一列,产生所需的:

00
01
11

【讨论】:

  • 你可能会使用np.lexsort,但我不确定它会比字符串/空转换更快。
【解决方案3】:

为每个row 创建一个string,然后应用np.sort()

如果我们有一个array 来测试:

a = np.array([[1,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,1,1]])

我们可以使用np.apply_along_axis为每个row创建strings

a = np.apply_along_axis(lambda r: ''.join([str(c) for c in r]), 0, a)

现在会变成a

array(['1010', '0010', '0011', '0011'], dtype='<U4')

所以现在我们可以sort stringsnp.sort():

a = np.sort(a)

制作a:

array(['0010', '0011', '0011', '1010'], dtype='<U4')

然后我们可以使用以下命令转换回原始格式:

a = np.array([[int(i) for i in r] for r in a])

这使得a:

array([[0, 0, 1, 0],
       [0, 0, 1, 1],
       [0, 0, 1, 1],
       [1, 0, 1, 0]])

如果你想把这一切塞进一行:

a = np.array([[int(i) for i in r] for r in np.sort(np.apply_along_axis(lambda r: ''.join([str(c) for c in r]), 0, a))])

【讨论】:

  • 感谢您的建议。将单个位转换为字符字节似乎是“错误的”。也许numpy.packbits()每行到64个uint8然后用你的方法?
【解决方案4】:

这很慢,但可以完成工作。

def sort_col(arr, col_num=0):
# if we have sorted over all columns return array
if col_num >= arr.shape[1]:
    return arr

# sort array over given column
arr_sorted = arr[arr[:, col_num].argsort()]

# if the number of 1s in the given column is not equal to the total number
# of rows neither equal to 0, split on 1 and 0, sort and then merge
if len(arr) > np.sum(arr_sorted[:, col_num]) > 0:
    arr_sorted0s = sort_col(arr_sorted[arr_sorted[:, col_num]==0], col_num+1)
    arr_sorted1s = sort_col(arr_sorted[arr_sorted[:, col_num]==1], col_num+1)
    # change order of stacking if you want ascenting order
    return np.vstack((arr_sorted0s, arr_sorted1s))

# if the number of 1s in the given column is equal to the total number
# of rows or equal to 0, just go to the next iteration
return sort_col(arr_sorted, col_num + 1)



np.random.seed(0)
a = np.random.randint(0, 2, (5, 4))
print(a)
print(sort_col(a))

# prints
[[0 1 1 0]
 [1 1 1 1]
 [1 1 1 0]
 [0 1 0 0]
 [0 0 0 1]]
[[0 0 0 1]
 [0 1 0 0]
 [0 1 1 0]
 [1 1 1 0]
 [1 1 1 1]]

编辑。或者更好的是使用 Daniels 解决方案。在发布我的代码之前,我没有检查新的答案。

【讨论】:

    猜你喜欢
    • 2019-12-11
    • 2018-03-31
    • 1970-01-01
    • 2014-03-02
    • 1970-01-01
    • 2013-10-07
    • 2013-12-16
    • 2017-06-30
    • 2020-01-14
    相关资源
    最近更新 更多