Shifting the Elements of an Array in Python

Efficient way to rotate a list in python

A collections.deque is optimized for pulling and pushing on both ends. They even have a dedicated rotate() method.

from collections import deque
items = deque([1, 2])
items.append(3) # deque == [1, 2, 3]
items.rotate(1) # The deque is now: [3, 1, 2]
items.rotate(-1) # Returns deque to original state: [1, 2, 3]
item = items.popleft() # deque == [2, 3]

Python program to shift elements of array

The problem comes from the line:

tempArray = finalValues[-1]

You don't create a copy of the previous list, but only a new name to refer to it. After that, all changes you make to tempArray are actually changes to this list, and when you finally do:

finalValues.append(tempArray)

you just add another reference to this same list in finalValues.

In the end, finalValues contains 4 references to the same list, which you can access with finalValues[0], finalValues[1]...

What you need is to create a new list by copying the previous one. One way to do it is to use a slice:

tempArray = finalValues[-1][:]

You can find other ways to close or copy a list in this question

And so, the complete code gives the expected output:

Last array of final:  [1, 2, 3, 4]
Temp Array: [1, 2, 3, 4]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1]]
Last array of final: [2, 3, 4, 1]
Temp Array: [2, 3, 4, 1]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2]]
Last array of final: [3, 4, 1, 2]
Temp Array: [3, 4, 1, 2]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]

[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]

Shifting Elements in a Numpy Array

Here is a more or less comprehensive function to solve it:

def shift(a, i, j, dir, n, fill=0, inplace=False):
out = a
if not inplace:
out = a.copy()
if dir == 'down':
if n < 0:
return shift(out, i, j, 'up', -n, fill=fill, inplace=True)
n = min(n, a.shape[0] - i)
out[i+n:, j] = a[i:a.shape[0] - n, j]
out[i:i+n, j] = fill
elif dir == 'up':
if n < 0:
return shift(out, i, j, 'down', -n, fill=fill, inplace=True)
n = min(n, i+1)
out[:i+1-n, j] = a[n:i+1, j]
out[i+1-n:i+1, j] = fill
elif dir == 'right':
if n < 0:
return shift(out, i, j, 'left', -n, fill=fill, inplace=True)
n = min(n, a.shape[1] - j)
out[i, j+n:] = a[i, j:a.shape[1] - n]
out[i, j:j+n] = fill
elif dir == 'left':
if n < 0:
return shift(out, i, j, 'right', -n, fill=fill, inplace=True)
n = min(n, j+1)
out[i, :j+1-n] = a[i, n:j+1]
out[i, j+1-n:j+1] = fill
else:
raise ValueError('Unknown direction "{}".'.format(dir))
return out

Some tests:

import numpy as np

arr = np.arange(25).reshape((5, 5))
print(arr)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 12 13 14]
# [15 16 17 18 19]
# [20 21 22 23 24]]
print(shift(arr, 2, 1, 'up', 2))
# [[ 0 11 2 3 4]
# [ 5 0 7 8 9]
# [10 0 12 13 14]
# [15 16 17 18 19]
# [20 21 22 23 24]]
print(shift(arr, 2, 1, 'left', -2))
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 0 0 11 12]
# [15 16 17 18 19]
# [20 21 22 23 24]]
print(shift(arr, 2, 1, 'down', 1, fill=100))
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [ 10 100 12 13 14]
# [ 15 11 17 18 19]
# [ 20 16 22 23 24]]
shift(arr, 2, 1, 'right', 3, inplace=True)
print(arr)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 0 0 0 11]
# [15 16 17 18 19]
# [20 21 22 23 24]]

EDIT

Following discussion in comments, I add another function(s) to solve the problem of shifting "sliding tiles":

import numpy as np

def shift_vector(v, i, n, empty=0):
if n < 0:
return shift_vector(v[::-1], len(v) - i - 1, -n)[::-1]
if n < len(v) - i:
# Find n empty places after i
idx = np.where(np.cumsum(v[i + 1:] == empty) == n)[0]
last_zero_idx = idx[0] if len(idx) > 0 else len(v) - i - 1
# Get non-empty values
v_slice = v[i + 1:i + last_zero_idx + 1]
values = v_slice[np.where(v_slice != empty)[0]]
# Copy to vector
v[i + n] = v[i]
r = range(i + n + 1, min(i + last_zero_idx + 2, len(v)))
v[r] = values[:len(r)]
v[i:i + n] = empty
return v

def shift(a, i, j, dir, n, empty=0, inplace=False):
out = a
if not inplace:
out = a.copy()
if dir == 'down':
out[:, j] = shift_vector(out[:, j], i, n, empty=empty)
elif dir == 'up':
out[:, j] = shift_vector(out[:, j], i, -n, empty=empty)
elif dir == 'right':
out[i, :] = shift_vector(out[i, :], j, n, empty=empty)
elif dir == 'left':
out[i, :] = shift_vector(out[i, :], j, -n, empty=empty)
else:
raise ValueError('Unknown direction "{}".'.format(dir))
return out


m = np.array([[1, 0, 0, 2],
[3, 4, 0, 0],
[5, 0, 6, 7],
[0, 8, 9, 0]])
print("m")
print(m)
print("shift(m, 1, 0, 'right', 2)")
print(shift(m, 1, 0, 'right', 2))
print("shift(m, 3, 1, 'down', -2)")
print(shift(m, 3, 1, 'down', -2))
print("shift(m, 0, 3, 'left', 3)")
print(shift(m, 0, 3, 'left', 3))
print("shift(m, 2, 2, 'up', 1)")
print(shift(m, 2, 2, 'up', 1))

Output:

m
[[1 0 0 2]
[3 4 0 0]
[5 0 6 7]
[0 8 9 0]]
shift(m, 1, 0, 'right', 2)
[[1 0 0 2]
[0 0 3 4]
[5 0 6 7]
[0 8 9 0]]
shift(m, 3, 1, 'down', -2)
[[1 4 0 2]
[3 8 0 0]
[5 0 6 7]
[0 0 9 0]]
shift(m, 0, 3, 'left', 3)
[[2 0 0 0]
[3 4 0 0]
[5 0 6 7]
[0 8 9 0]]
shift(m, 2, 2, 'up', 1)
[[1 0 0 2]
[3 4 6 0]
[5 0 0 7]
[0 8 9 0]]

Move all array elements one space up in python NumPy

You could use numpy.roll() with a subset assignment:

arr = numpy.array([2,4,5,6])

arr[:3] = numpy.roll(arr[:3],1)

print(arr)
[5 2 4 6]

In Python, is there a way to move/shift down elements from one array to another based on another array?

Here's a solution that doesn't use any packages:

def transpose(m):
return [list(x) for x in zip(*m)]

def apply_shifts(m, shifts):
m_t = transpose(m)
for i in range(len(shifts)):
padding = (max(shifts) - shifts[i])
m_t[i] = shifts[i] * [0] + m_t[i] + padding * [0]
return transpose(m_t)

If we try it using your example, we can see the function behaves correctly:

>>> m = [
... [0, 1, 2],
... [3, 4, 5],
... [6, 7, 8],
... ]
>>> shifts = [0, 1, 2]
>>> apply_shifts(m, shifts)
[
[0, 0, 0],
[3, 1, 0],
[6, 4, 2],
[0, 7, 5],
[0, 0, 8]
]

How to shift array symmetrically in numpy

You could add +1 to all elements that appear before zero or are equal to zero, and add -1 to all elements that follow zero. Here is one approach:

values = np.array([1, 0.34, 0.59, 0.40, 0.94])
a = np.array([0,1,2,3,4])
for i in range(a.size):
print(values[a])
a += 2 * (np.arange(a.size) <= a.argmin()) - 1

# [1. 0.34 0.59 0.4 0.94]
# [0.34 1. 0.34 0.59 0.4 ]
# [0.59 0.34 1. 0.34 0.59]
# [0.4 0.59 0.34 1. 0.34]
# [0.94 0.4 0.59 0.34 1. ]

Alternatively, you could use np.arange with negative range and take absolute values:

for i in range(0, -5, -1):
print(np.abs(np.arange(i,i+5)))

You could also generate a matrix of all shifted rows in one go:

mat = np.eye(5, dtype=int).cumsum(0).cumsum(0)
mat += np.rot90(mat, k=2) - np.eye(5, dtype=int) - 1

[[0 1 2 3 4]
[1 0 1 2 3]
[2 1 0 1 2]
[3 2 1 0 1]
[4 3 2 1 0]]


Related Topics



Leave a reply



Submit