在numpy数组中有效地交换元素

时间:2015-02-20 11:31:42

标签: python arrays performance numpy

假设我们有一个大矩阵 A ,并且两个矩阵元素的索引(c1,r1)(c2,r2)我们要交换:

import numpy as np
A = np.random.rand(1000,1000)
c1, r1 = 10, 10
c2, r2 = 20, 40

这样做的pythonic方法是:

A[c1, r1], A[c2, r2] = A[c2, r2], A[c1, r1]

但是,如果您想进行大量的交换,这种解决方案可能会很慢。

是否有更有效的方法在numpy数组中交换两个元素?

提前致谢。

2 个答案:

答案 0 :(得分:2)

初步答复,不起作用

您可以通过使用索引数组(c1,r1,c2,r2)轻松​​地对交换操作进行矢量化,而不是迭代标量索引列表。

c1 = np.array(<all the "c1" values>, dtype=int)
r1 = np.array(<all the "r1" values>, dtype=int)
c2 = np.array(<all the "c2" values>, dtype=int)
r2 = np.array(<all the "r2" values>, dtype=int)
A[c1, r1], A[c2, r2] = A[c2, r2], A[c1, r1]

请注意,如果交换的顺序有所不同,则可以一次性执行所有交换,这可以与迭代不同。因此,这不是您问题的有效答案

E.g。用p2交换p1,然后用p3交换p2,不同于交换p1和p2,以及p2和p3。在后一种情况下,p1和p3都得到p2的原始值,而p2得到p1和p3之间值的 last ,即p3(根据它们出现在索引数组中的顺序) )。

然而,由于它是你所追求的速度,所以必须采用矢量化操作(以某种方式)。


为上述解决方案添加正确性

那么我们如何才能进行矢量化交换,同时获得我们需要的输出?我们可以采用混合方法,将索引列表分成块(尽可能少),其中每个块只包含唯一的点,从而保证顺序没有区别。交换每个块都是vercrorized-ly,我们只迭代块。

这里有一个如何运作的草图:

c1, r1 = np.array([ np.arange(10), np.arange(10) ])
c2, r2 = np.array([ [2,4,6,8,0,1,3,5,7,9], [9,1,6,8,2,2,2,2,7,0] ])
A = np.empty((15,15))

def get_chunk_boundry(c1, r1, c2, r2):
    a1 = c1 + 1j * r1
    a2 = c2 + 1j * r2
    set1 = set()
    set2 = set()
    for i, (x1, x2) in enumerate(zip(a1, a2)):
        if x1 in set2 or x2 in set1:
            return i
        set1.add(x1); set2.add(x2)
    return len(c1)

while len(c1) > 0:
    i = get_chunk_boundry(c1, r1, c2, r2)
    c1b = c1[:i]; r1b = r1[:i]; c2b = c2[:i]; r2b = r2[:i]
    print 'swapping %d elements' % i
    A[c1b,r1b], A[c2b,r2b] = A[c2b,r2b], A[c1b,r1b]
    c1 = c1[i:]; r1 = r1[i:]; c2 = c2[i:]; r2 = r2[i:]

如果将索引存储为2dim数组(N x 4),则此处的切片速度会更快。

答案 1 :(得分:1)

这是一个迭代解决方案,我为了参考目的而编写(作为处理可能重复项目的一种方法):

def iter2(A, rc1, rc2):
    for r,c in zip(rc1.T, rc2.T):
        j,k = tuple(r),tuple(c)
        A[j],A[k] = A[k],A[j]
    return A

例如,如果:

N = 4
Aref=np.arange(N)+np.arange(N)[:,None]*10

rc1=np.array([[0,0,0],[0,3,0]])
rc2=np.array([[3,3,2],[3,0,2]])

然后

 print(iter2(A.copy(), rc1,rc2))

生成角落交换,然后交换(2,2):

[[22  1  2 30]
 [10 11 12 13]
 [20 21 33 23]
 [ 3 31 32  0]]

shx2's解决方案似乎做了同样的事情,但是对于我的测试用例,在分块函数中似乎存在错误。

对于shx2's测试(15,15)数组,我的迭代解决方案快4倍!它正在进行更多交换,但每次交换的工作量更少。对于较大的阵列,我希望分块更快,但我不知道我们必须走多远。它还取决于重复模式。

我的数组的哑向量化交换是:

A[tuple(rc1)],A[tuple(rc2)] = A[tuple(rc2)],A[tuple(rc1)]

在这个(15,15)示例中,它只比我的迭代解决方案快20%。显然,我们需要一个非常大的测试用例来产生一些严肃的时间。


在1d阵列上运行时,迭代解决方案更快,更简单。即使进行了散乱和重塑,这个功能也是我发现的最快的功能。我没有在Cython上获得太多的速度提升。 (但关于数组大小的注意事项仍然适用。)

def adapt_1d(A, rc1, rc2, fn=None):
    # adapt a multidim case to use a 1d iterator
    rc2f = np.ravel_multi_index(rc2, A.shape)
    rc1f = np.ravel_multi_index(rc1, A.shape)
    Af = A.flatten()
    if fn is None:
        for r,c in zip(rc1f,rc2f):
            Af[r],Af[c] = Af[c],Af[r]
    else:
        fn(Af, rc1f, rc2f)
    return Af.reshape(A.shape)