Reputation: 1694
Assuming that we have a large matrix A, and the indices of two matrix elements (c1, r1), (c2, r2) that we want to swap:
import numpy as np
A = np.random.rand(1000,1000)
c1, r1 = 10, 10
c2, r2 = 20, 40
The pythonic way to do so would be:
A[c1, r1], A[c2, r2] = A[c2, r2], A[c1, r1]
However, this solution can be slow if you want to do a large number of swappings.
Is there a more efficient way to swap two elements in a numpy array?
Thanks in advance.
Upvotes: 6
Views: 10459
Reputation: 231365
Here's an iterative solution, which I wrote for reference purposes (as one way to deal with possible repeated items):
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
For example, if:
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]])
then
print(iter2(A.copy(), rc1,rc2))
produces a swap of the corners, followed by a swap with (2,2):
[[22 1 2 30]
[10 11 12 13]
[20 21 33 23]
[ 3 31 32 0]]
shx2's
solution seems to do the same thing, though for my test case there seems to be bug in the chunking function.
For the shx2's
test (15,15)
array, my iterative solution is 4x faster! It is doing more swaps, but less work per swap. For larger arrays I expect the chunking to be faster, but I don't know how much larger we'd have to go. It will also depend on the repeat pattern.
The dumb vectorized swap with my arrays is:
A[tuple(rc1)],A[tuple(rc2)] = A[tuple(rc2)],A[tuple(rc1)]
In this (15,15) example, it's only 20% faster than my iterative solution. Clearly we need a much large test case to produce some serious timings.
The iterative solution is faster, and simpler, when operating on a 1d array. Even with the raveling and reshaping this function is the fastest that I've found. I'm not getting much of a speed improvement in Cython over this. (but cautions about arrays sizes still apply.)
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)
Upvotes: 1
Reputation: 64308
You can easily vectorize the swap operation, by using arrays of indexes (c1, r1, c2, r2) instead of iterating over lists of scalar indices.
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]
Note this performs all the swaps in one go, which can be different than iteratively, if the order of the swapping makes a difference. For this reason, this is not a valid answer to your question.
E.g. swapping p1 with p2, then p2 with p3, is different from swapping p1 and p2, and p2 and p3 in one go. In the latter case, both p1 and p3 get the original value of p2, and p2 gets the last of the values between p1 and p3, i.e. p3 (according to the order they appear in the index-array).
However, since it is speed you're after, vectorizing the operation (in some way) must be the way to go.
So how can we perform vectorized swapping, while getting the output we need? We can take a hybrid approach, by breaking the lists of indexes into chunks (as few as possible), where each chunk only contains unique points, thus guaranteeing the order makes no difference. Swapping each chunk is done vercrorized-ly, and we only iterate over the chunks.
Here's a sketch of how this can work:
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:]
Slicing here will be faster if you store the indices as a 2dim array (N x 4) to begin with.
Upvotes: 3