Given a two dimensional matrix, e.g.
l = [[1,1,1],
[2,5,2],
[3,3,3]])
What is the most efficient way of implementing a shift operation on columns and rows?
E.g.
shift('up', l)
[[2, 5, 2],
[3, 3, 3],
[1, 1, 1]]
but
shift('left', l)
[[1, 1, 1],
[5, 2, 2],
[3, 3, 3]]
I'm using collections.deque
on both depths because of this answer but while a 'up' or 'down' only requires 1 shift, a 'left' or 'right' requires N shifts (my implementation is using a for cycle for each row).
In C I think this can be improved using pointer arithmetic (see e.g. this answer).
Is there a better pythonic way?
EDIT:
Thanks to martineau for pointing out these important points of the question. I'm sorry I didn't pointed them out before.
Numpy provides a method called roll() to shift entries.
>>> import numpy as np
>>> x = np.arange(9)
>>> x = x.reshape(3, 3)
>>> print(x)
[[0 1 2]
[3 4 5]
[6 7 8]]
>>> x = np.roll(x, -1, axis=0) # up
>>> print(x)
[[3 4 5]
[6 7 8]
[0 1 2]]
>>> x = np.roll(x, 1, axis=0) # down
>>> print(x)
[[0 1 2]
[3 4 5]
[6 7 8]]
>>> x = np.roll(x, 2, axis=1) # right
>>> print(x)
[[1 2 0]
[4 5 3]
[7 8 6]]
>>> x = np.roll(x, -2, axis=1) # left
>>> print(x)
[[0 1 2]
[3 4 5]
[6 7 8]]
I guess that Numpy will be pretty efficient compared to most solutions
in terms of matrix operations and you won't be bound to a 2 dimensional matrix.
Here's one fairly efficient way to do it that will work with non-square matrices:
DIRS = NONE, UP, DOWN, LEFT, RIGHT = 'unshifted', 'up', 'down', 'left', 'right'
def shift(matrix, direction, dist):
""" Shift a 2D matrix in-place the given distance of rows or columns in the
specified (NONE, UP, DOWN, LEFT, RIGHT) direction and return it.
"""
if dist and direction in (UP, DOWN, LEFT, RIGHT):
n = 0
if direction in (UP, DOWN):
n = (dist % len(matrix) if direction == UP else -(dist % len(matrix)))
elif direction in (LEFT, RIGHT):
n = (dist % len(matrix[0]) if direction == LEFT else -(dist % len(matrix[0])))
matrix[:] = list(zip(*matrix)) # Transpose rows and columns for shifting.
h = matrix[:n]
del matrix[:n]
matrix.extend(h)
if direction in (LEFT, RIGHT):
matrix[:] = map(list, zip(*matrix)) # Undo previous transposition.
return matrix
if __name__ == '__main__':
# Some non-square test matrices.
matrix1 = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]
matrix2 = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]
def shift_and_print(matrix, direction, dist):
GAP = 2 # Plus one for a ":" character.
indent = max(map(len, DIRS)) + GAP
print(direction
+ ': ' + (indent-2-len(direction))*' '
+ ('\n'+indent*' ').join(map(str, shift(matrix, direction, dist)))
+ '\n')
for matrix in matrix1, matrix2:
for direction in DIRS:
shift_and_print(matrix, direction, 1) # Printed results are cumulative.
Output (note that the results are cumulative since the operations are performed in-place and the shifting is applied to the result of the previous call):
no shift: [1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10, 11, 12]
up: [4, 5, 6]
[7, 8, 9]
[10, 11, 12]
[1, 2, 3]
down: [1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10, 11, 12]
left: [2, 3, 1]
[5, 6, 4]
[8, 9, 7]
[11, 12, 10]
right: [1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10, 11, 12]
no shift: [1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
up: [5, 6, 7, 8]
[9, 10, 11, 12]
[1, 2, 3, 4]
down: [1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
left: [2, 3, 4, 1]
[6, 7, 8, 5]
[10, 11, 12, 9]
right: [1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With