4

I'd like to return a 2D numpy.array with multiple rolls of a given 1D numpy.array.

>>> multiroll(np.arange(10), [-1, 0, 1, 2])
array([[1., 0., 9., 8.],
       [2., 1., 0., 9.],
       [3., 2., 1., 0.],
       [4., 3., 2., 1.],
       [5., 4., 3., 2.],
       [6., 5., 4., 3.],
       [7., 6., 5., 4.],
       [8., 7., 6., 5.],
       [9., 8., 7., 6.],
       [0., 9., 8., 7.]])

Is there some combination of numpy.roll, numpy.tile, numpy.repeat, or other functions that does this?

Here's what I've tried

def multiroll(array, rolls):
    """Create multiple rolls of 1D vector"""
    m = len(array)
    n = len(rolls)
    shape = (m, n)
    a = np.empty(shape)
    for i, roll in enumerate(rolls):
        a[:,i] = np.roll(array, roll)
    return a

I'd expected there's a more "Numpythonic" way of doing this that doesn't use the loop.

0

3 Answers 3

8

Approach #1 : For elegance

Here's one way with broadcasting -

In [44]: a
Out[44]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [45]: rolls
Out[45]: array([-1,  0,  1,  2])

In [46]: a[(np.arange(len(a))[:,None]-rolls) % len(a)]
Out[46]: 
array([[1, 0, 9, 8],
       [2, 1, 0, 9],
       [3, 2, 1, 0],
       [4, 3, 2, 1],
       [5, 4, 3, 2],
       [6, 5, 4, 3],
       [7, 6, 5, 4],
       [8, 7, 6, 5],
       [9, 8, 7, 6],
       [0, 9, 8, 7]])

Approach #2 : For memory/perf-efficiency

Idea mostly borrowed from - this post.

We can leverage np.lib.stride_tricks.as_strided based scikit-image's view_as_windows to get sliding windows. More info on use of as_strided based view_as_windows.

from skimage.util.shape import view_as_windows

def multiroll_stridedview(a, r):
    r = np.asarray(r)

    # Concatenate with sliced to cover all rolls
    a_ext = np.concatenate((a,a[:-1]))

    # Get sliding windows; use advanced-indexing to select appropriate ones
    n = len(a)
    return view_as_windows(a_ext,n)[:,(n-r)%n]
Sign up to request clarification or add additional context in comments.

Comments

2

Approach #3 : For mathematical beauty (and efficiency ?)

Using a fft kernel in the frequency domain you can process a whole matrix at once. This method only work with integer

A = np.array([[1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.],
       [8., 8., 8., 8.],
       [9., 9., 9., 9.],
       [0., 0., 0., 0.]]).transpose()

m,n = A.shape
#shift vector
s=[-1,0,1,2] 
#transformation kernel (shift theorem)
fftkernel = np.exp(-2*1j*np.pi/n*np.outer(v,np.arange(0,n)))
#Apply the shift
res=np.round(np.fft.ifft(np.fft.fft(A,axis = 1) * fftkernel ,axis = 1)).real.transpose()

We get:

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

You can get more information about how this code work here

For left circular shift you can use:

fftkernel = np.exp(2*1j*np.pi/n*np.outer(v,np.arange(0,n)))

Without minus sign.

Comments

0

Another method based on this answer.


arr = np.tile(vec, (len(vec),1))
output = list(map(np.roll, np.squeeze(arr, 0), rolls))

Comments

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.