1

Problem

I have a 2D NumPy array, arr, and for each row, I would like to reverse a section of the array. Crucially, for each row, the start and stop indices must be unique. I can achieve this using the following.

import numpy as np

arr = np.repeat(np.arange(10)[np.newaxis, :], 3, axis=0)
reverse = np.sort(np.random.choice(arr.shape[1], [arr.shape[0], 2], False))

# arr
# array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
#        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])

# reverse
# array([[1, 7],
#        [8, 9],
#        [4, 6]])

Reverse each row between the start, stop indices in `reverse.

for idx, (i, j) in enumerate(reverse):
    arr[idx, i:j+1] = arr[idx, i:j+1][::-1]

# arr 
# array([[0, 7, 6, 5, 4, 3, 2, 1, 8, 9],
#        [0, 1, 2, 3, 4, 5, 6, 7, 9, 8],
#        [0, 1, 2, 3, 6, 5, 4, 7, 8, 9]])

Question

Is this possible using basic slicing and indexing? I tried to use the output of reverse to form multiple slice objects, but was unsuccessful.


Update

A simple comparison of the original method vs answer. For my data, the solution is only required to deal with 2D matrices with shape (50, 100).

import numpy as np

def reverse_one(arr, n): 
    temp = np.repeat(arr.copy(), n, axis=0)
    reverse = np.sort(np.random.choice(temp.shape[1], [n, 2], False))

    for idx, (i, j) in enumerate(reverse):
        temp[idx, i:j+1] = temp[idx, i:j+1][::-1]

    return temp

def reverse_two(arr, n):
    temp = np.repeat(arr.copy(), n, axis=0)
    reverse = np.sort(np.random.choice(temp.shape[1], [n, 2], False))
    rev = np.ravel_multi_index((np.arange(n)[:, np.newaxis], reverse), temp.shape)
    rev[:, 1] += 1
    idx = np.arange(temp.size).reshape(temp.shape)
    s = np.searchsorted(rev.ravel(), idx, 'right')
    m = (s % 2 == 1)
    g = rev[s[m] // 2]
    idx[m] = g[:, 0] - (idx[m] - g[:, 1]) - 1

    return temp.take(idx)

m = 100
arr = np.arange(m)[np.newaxis, :]

print("reverse_one:")
%timeit reverse_one(arr, m//2)
print("=" * 40)
print("reverse_two:")
%timeit reverse_two(arr, m//2)

Running the following code in a Jupyter Notebook gives the following results.

reverse_one:
1000 loops, best of 5: 202 µs per loop
========================================
reverse_two:
1000 loops, best of 5: 363 µs per loop
1
  • What do you mean by "advanced indexing"? arr[[0,1,2]] = arr[[2,1,0]] instead of arr[:]=arr[::-1]? Commented Jan 28, 2020 at 17:26

1 Answer 1

1

This was kinda tricky but I figured out one way to do it. Advanced indexing is expensive though so you'd have to see whether it is really faster or not depending on the data that you have.

import numpy as np

np.random.seed(0)
arr = np.repeat(np.arange(10)[np.newaxis, :], 3, axis=0)
reverse = np.sort(np.random.choice(arr.shape[1], [arr.shape[0], 2], False))
print(arr)
# [[0 1 2 3 4 5 6 7 8 9]
#  [0 1 2 3 4 5 6 7 8 9]
#  [0 1 2 3 4 5 6 7 8 9]]
print(reverse)
# [[2 8]
#  [4 9]
#  [1 6]]

# Get "flat" indices of the bounds
rev = np.ravel_multi_index((np.arange(arr.shape[0])[:, np.newaxis], reverse), arr.shape)
# Add one to the second bound (so it is first index after the slice)
rev[:, 1] += 1
# Make array of flat indices for the data
idx = np.arange(arr.size).reshape(arr.shape)
# Find the position of flat indices with respect to bounds
s = np.searchsorted(rev.ravel(), idx, 'right')
# For each "i" within a slice, "s[i]" is odd
m = (s % 2 == 1)
# Replace indices within slices with their reversed ones
g = rev[s[m] // 2]
idx[m] = g[:, 0] - (idx[m] - g[:, 1]) - 1
# Apply indices to array
res = arr.take(idx)
print(res)
# [[0 1 8 7 6 5 4 3 2 9]
#  [0 1 2 3 9 8 7 6 5 4]
#  [0 6 5 4 3 2 1 7 8 9]]
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I will check and update with a speed comparison

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.