Python: How do I change items between lists? - python

I'm trying to code the 2048 game, and I'm stuck at the move up/down part.
So ex, if I've a list like this:
2 0 2 4
0 8 2 4
4 4 2 0
0 4 0 2
I want to move my numbers up, so I've something like this:
2 8 2 4
4 4 2 4
0 4 2 2
0 0 0 0
And I don't know how to even begin, someone can give me tips?
I tried this but it only works for a identity matrix:
new_matrix = []
for line in matrix:
for pos, element in enumerate(line):
if element != 0:
new_matrix.append(element)
line[pos] = 0
list.clear(matrix[0])
matrix[0] = new_matrix
return (matrix)

You can iterate through the list, check which index contains 0, let's say (n) then assign the value of the next elements nth index, and make next elements nth index 0:
A = [[1,0,2,1],[0,1,3,4],[4,5,1,0],[0,3,0,1]]
length = len(A)
for i, elem in enumerate(A):
for j, item in enumerate(elem):
if item == 0 and i + 1 < length:
A[i][j] = A[i+1][j]
A[i+1][j] = 0
print(A)
Prints:
[[1, 1, 2, 1], [4, 5, 3, 4], [0, 3, 1, 1], [0, 0, 0, 0]]
For the current example:
>>> A
[[2, 0, 2, 4],
[0, 8, 2, 4],
[4, 4, 2, 0],
[0, 4, 0, 2]]
>>> for i, elem in enumerate(A):
for j, item in enumerate(elem):
if item == 0 and i + 1 < length:
A[i][j] = A[i+1][j]
A[i+1][j] = 0
>>> A
[[2, 8, 2, 4],
[4, 4, 2, 4],
[0, 4, 2, 2],
[0, 0, 0, 0]]

Related

How to iterate through list infinitely with +1 offset each loop

I want to infinitely iterate through the list from 0 to the end, but in the next loop I want to start at 1 to the end plus 0, and the next loop would start at 2 to the end plus 0, 1, up to the last item where it would start again at 0 and go to the end.
Here is my code:
a = [ 0, 1, 2 ]
offset = 0
rotate = 0
while True:
print(a[rotate])
offset += 1
rotate += 1
if offset >= len(a):
offset = 0
rotate += 1
if rotate >= len(a):
rotate = 0
This is the solution I came up with so far. It's far from perfect.
The result that I want is:
0, 1, 2 # first iteration
1, 2, 0 # second iteration
2, 0, 1 # third iteration
0, 1, 2 # fourth iteration
and so on.
You can use a deque which has a built-in and efficient rotate function (~O(1)):
>>> d = deque([0,1,2])
>>> for _ in range(10):
... print(*d)
... d.rotate(-1) # negative -> rotate to the left
...
0 1 2
1 2 0
2 0 1
0 1 2
1 2 0
2 0 1
0 1 2
1 2 0
2 0 1
0 1 2
Try this:
a = [0, 1, 2]
while True:
print(*a, sep=', ')
a.append(a[0])
a.pop(0)
Output:
0, 1, 2
1, 2, 0
2, 0, 1
0, 1, 2
1, 2, 0
2, 0, 1
...
Or, pop returns the element removed, so it can be simplified
a = [0, 1, 2]
while True:
print(*a, sep=', ')
a.append(a.pop(0))
[Thanks ShadowRanger and Tomerikoo for improvement suggestions.]
You can create the lists with offsets using list slicing, and then repeat them infinitely using itertools.cycle(). This computes all of the rotations exactly once, and then cycles through all of them:
from itertools import cycle, islice
lst = [0, 1, 2]
items = [lst[i:] + lst[:i] for i in range(len(lst))]
iterator = cycle(items)
for item in islice(iterator, 10):
print(item)
The above approach is fast once you've gotten past the precomputation, but you may (depending on your use case) prefer an approach that does not have an upfront time/space cost. In that case, you can use a generator instead:
from itertools import cycle, islice
def rotate(lst):
for offset in cycle(range(len(lst))):
yield lst[offset:] + lst[:offset]
lst = [0, 1, 2]
for item in islice(rotate(lst), 10):
print(item)
Both of these output:
[0, 1, 2]
[1, 2, 0]
[2, 0, 1]
[0, 1, 2]
[1, 2, 0]
[2, 0, 1]
[0, 1, 2]
[1, 2, 0]
[2, 0, 1]
[0, 1, 2]
These code snippets have been improved from a suggestion by wjandrea.
Here you have another alternative using pointers:
a = [ 0, 1, 2 ]
i = 0
l = len(a)
while True:
out = []
for j in range(i, i+l):
out.append(a[j%l])
print(out)
i=(i+1)%l
Output:
[0, 1, 2]
[1, 2, 0]
[2, 0, 1]
[0, 1, 2]
[1, 2, 0]
[2, 0, 1]
Another option, using list slicing:
cycles = [a[i:]+a[:i] for i, _ in enumerate(a)]
while True:
for c in cycles: print(c)
Or, if you don't want to precalculate O(n^2) space for cycles, you can keep making the cycles afresh:
from itertools import count
n = len(a)
for i in count():
j = i%n
print(a[j:]+a[:j])

Retrieve indices of certain value in a matrix and put it in a nested list

I have this matrix; let's say there are hundreds of x value and thousands of y lists inside this matrix:
[[100 0 0 ... 0 0 0]
[ 0 100 0 ... 0 0 0]
[ 0 0 100 ... 0 0 0]
...
[ 0 0 0 ... 100 0 0]
[ 0 0 0 ... 0 100 0]
[ 0 0 0 ... 0 0 100]]
How will I be able to retrieve the index of value >= 90 and put it in a nested list?
Here is the sample output:
[[0], [1, 167], [2, 498, 2890] ... [6568, 99998], [7894, 19695, 99999], [873, 100000]]
Try using nested list comprehension:
[[idx for idx, value in enumerate(row) if value >= 90] for row in arr]
To have the result of #azro's, I'd rather use:
np.argwhere(arr >= 90)
The post numpy get index where value is true might help
numpy.transpose((array > value).nonzero())
import numpy as np
values = np.array([[1, 76, 987, 1, 1, 1], [876, 76, 765, 1, 1, 1], [1, 3, 4, 1, 1, 1],
[1, 3, 4, 1, 1, 1], [1, 3, 4, 1, 1, 1], [1, 3, 4, 1, 1, 123]])
result = np.transpose((values > 90).nonzero())
print(result)
[[0 2]
[1 0]
[1 2]
[5 5]]

How to return an array of anti-diagonals or diagonals of given N*N square matrix

I need to print the diagonals of matrix
if diagonals then i to i + 1 and j to j -1
if anti-diagonals then i to i-1 and j to j + 1
matrix is
A = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
Expected out is
1 0 0
2 4 0
3 5 7
6 8 0
9 0 0
Code is below for diagonal
def print_diagonal(A):
m = len(A)
n = len(A[0])
#result = [[0 for i in range(m)] for i in range(n)]
result = []
for k in range(m):
i = k
j = 0
while i>=0:
result.append(A[i][j])
i = i -1
j = j + 1
for k in range(1,n):
i = m - 1
j = k
while (j <= n-1):
result.append(A[i][j])
i = i -1
j = j + 1
return result
A = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print_diagonal(A)
My output is
[[0, 0, 0], [0, 0, 0], [0, 0, 0], 1, 4, 2, 7, 5, 3, 8, 6, 9]
The numbers in my output is matching correctly, but its not coming in order as expected
The order of iteration was wrong, you were going from the bottom-left to the top-right each time. Also the length to fill with zeroes is based on the minimum of n and m since n or m can only be decreased at most min(n, m) times before it reaches 0.
def print_diagonal(A):
n = len(A)
m = len(A[0])
result = []
length = min(n, m)
# Top-left to top-right
for k in range(m):
result.append([0 for _ in range(length)])
i = 0
j = k
while j >= 0 and i < n:
result[-1][i] = A[i][j]
i = i + 1
j = j - 1
# Top-right to bottom-right
for k in range(1,n):
result.append([0 for _ in range(length)])
i = k
j = m - 1
while j >= 0 and i < n:
result[-1][i - k] = A[i][j]
i = i + 1
j = j - 1
return result
A = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(print_diagonal(A))
prints out [[1, 0, 0], [2, 4, 0], [3, 5, 7], [6, 8, 0], [9, 0, 0]].
Just create a matrix of 0s and then fill the diagonals one by one:
def print_diagonal(A):
m = len(A)
result = [[0]*m for _ in range(2*m-1)]
for i in range(m):
for k in range(i+1):
result[i][k] = A[k][i-k]
for i in range(0,m-1):
for k in range(m-1-i):
result[m+i][k] = A[i+k+1][m-1-k]
return result
The following algorithm takes advantage of the fact that the diagonals of a matrix are simply every (n-1)th element when iterating the columns of an nxn matrix from left to right and top to bottom and restricting the result to one element per row. I wrote the programme for a similar case, but omitting the leading zeros. However, I added the necessary rows to add fills. To work with nxm matrices as well, some adjustments are necessary.
def print_diagonal(A, zeros=True):
Al = np.concatenate(A)
d = max(np.array(A).shape)
results = []
for start in range(0, 2 * d - 1):
line = []
row = max(start - d + 1, 0)
p = start if row == 0 else (row + 1) * d - 1
while np.floor(p / d) == row and p < len(Al):
line.append(Al[p])
p = p + d - 1
row += 1
if zeros and len(line) < d:
line = [0] * (d - len(line)) + line if start < d else line + [0] * (d - len(line))
results.append(line)
return results
Given your example, the algorith has the following output:
print_diagonal(A, zeros=False) # [[1], [2, 4], [3, 5, 7], [6, 8], [9]]
print_diagonal(A, zeros=True) # [[0, 0, 1], [0, 2, 4], [3, 5, 7], [6, 8, 0], [9, 0, 0]]
You could make a function that extracts the first row and last columns (outer edge) to form the first elements in the diagonals, then repeat the process iteratively on the remaining sub-matrix padding with leading&trailing zeroes and adding elements to the diagonals at each iteration:
def diagsDownLeft(M):
diags,pad = [],[]
while any(M):
edge = [*M[0][:-1] ,*next(zip(*map(reversed,M)))]
M = [r[:-1] for r in M[1:]]
diags.append(pad+edge+pad)
pad.append(0)
return [*map(list,zip(*diags))]
Output (for any rectangular matrix):
A = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(diagsDownLeft(A))
[[1, 0, 0], [2, 4, 0], [3, 5, 7], [6, 8, 0], [9, 0, 0]]
B = [[1, 2, 3, 10],
[4, 5, 6, 11],
[7, 8, 9, 12]]
print(diagsDownLeft(B))
[[1, 0, 0], [2, 4, 0], [3, 5, 7], [10, 6, 8], [11, 9, 0], [12, 0, 0]]
C = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]
print(diagsDownLeft(C))
[[1, 0, 0], [2, 4, 0], [3, 5, 7], [6, 8, 10], [9, 11, 0], [12, 0, 0]]
How it works (visually):
1 2 3 1 2 3
4 5 6 x x 6
7 8 9 x x 9 ==> 1 2 3 6 9 (pad = none)
| | | | |
4 5 4 5 | | | | |
7 8 x 8 ==> 0 4 5 8 0 (pad = 0)
| | | | |
7 7 ==> 0 0 7 0 0 (pad = 0,0)
| | | | |
\ \ \ \ \__ [9,0,0] zipped
\ \ \ \___ [6,8,0]
\ \ \____ [3,5,7]
\ \_____ [2,4,0]
\______ [1,0,0]
If you also need the other diagonals:
def diagsDownRight(M):
diags,pad = [],[]
while any(M):
edge = [*next(zip(*reversed(M))), *M[0][1:]]
M = [r[1:] for r in M[1:]]
diags.append(pad+edge+pad)
pad.append(0)
return [*map(list,zip(*diags))]
Output:
print(diagsDownRight(A))
[[7, 0, 0], [4, 8, 0], [1, 5, 9], [2, 6, 0], [3, 0, 0]]
print(diagsDownRight(B))
[[7, 0, 0], [4, 8, 0], [1, 5, 9], [2, 6, 12], [3, 11, 0], [10, 0, 0]]
print(diagsDownRight(C))
[[10, 0, 0], [7, 11, 0], [4, 8, 12], [1, 5, 9], [2, 6, 0], [3, 0, 0]]
And opposite directions:
def diagsUpRight(M):
diags,pad = [],[]
while any(M):
edge = [*next(zip(*M)), *M[-1][1:]]
M = [r[1:] for r in M[:-1]]
diags.append(pad+edge+pad)
pad.append(0)
return [*map(list,zip(*diags))]
def diagsUpLeft(M):
diags,pad = [],[]
while any(M):
edge = [*M[-1][:-1],*next(zip(*map(reversed,M[::-1])))]
M = [r[:-1] for r in M[:-1]]
diags.append(pad+edge+pad)
pad.append(0)
return [*map(list,zip(*diags))]

numpy replace 2d bool array with sum of consecutive elements across an axis efficiently

I have a bool array (bool_arr) that I want to replace the consecutive non-zero numbers along the columns with their count (consecutive_count) (which is also the max/last number of the consecutive group)
bool_arr = consecutive_count =
[[1 1 1 1 0 1] [[3 6 1 6 0 1]
[1 1 0 1 1 0] [3 6 0 6 5 0]
[1 1 1 1 1 1] [3 6 3 6 5 2]
[0 1 1 1 1 1] [0 6 3 6 5 2]
[1 1 1 1 1 0] [2 6 3 6 5 0]
[1 1 0 1 1 1]] [2 6 0 6 5 1]]
I've created my own function that gets the cumulative sum of consecutive non-zero elements along the columns
consecutive_cumsum =
[[1 1 1 1 0 1]
[2 2 0 2 1 0]
[3 3 1 3 2 1]
[0 4 2 4 3 2]
[1 5 3 5 4 0]
[2 6 0 6 5 1]]
I currently use the following to get consecutive_count:
bool_arr = np.array([[1,1,1,1,0,1],
[1,1,0,1,1,0],
[1,1,1,1,1,1],
[0,1,1,1,1,1],
[1,1,1,1,1,0],
[1,1,0,1,1,1]])
consecutive_cumsum = np.array([[1,1,1,1,0,1],
[2,2,0,2,1,0],
[3,3,1,3,2,1],
[0,4,2,4,3,2],
[1,5,3,5,4,0],
[2,6,0,6,5,1]])
consecutive_count = consecutive_cumsum.copy()
for x in range(consecutive_count.shape[1]):
maximum = 0
for y in range(consecutive_count.shape[0]-1, -1, -1):
if consecutive_cumsum[y,x] > 0:
if consecutive_cumsum[y,x] < maximum: consecutive_count[y,x] = maximum
else: maximum = consecutive_cumsum[y,x]
else: maximum = 0
print(consecutive_count)
It works great but I am iterating over every element to replace with the max, between zeros.
Is there a way to use numpy to vectorize this instead of looping over all elements. And as a bonus, specify which axis (row vs column) it will perform it on
The new (v1.15.0 I believe) append and prepend keywords of np.diff make this easy:
bnd = np.diff(bool_arr, axis=0, prepend=0, append=0)
x, y = np.where(bnd.T)
bnd.T[x, y] *= (y[1::2]-y[::2]).repeat(2)
bnd[:-1].cumsum(axis=0)
# array([[3, 6, 1, 6, 0, 1],
# [3, 6, 0, 6, 5, 0],
# [3, 6, 3, 6, 5, 2],
# [0, 6, 3, 6, 5, 2],
# [2, 6, 3, 6, 5, 0],
# [2, 6, 0, 6, 5, 1]])
With selectable axis:
def count_ones(a, axis=-1):
a = a.swapaxes(-1, axis)
bnd = np.diff(a, axis=-1, prepend=0, append=0)
*idx, last = np.where(bnd)
bnd[(*idx, last)] *= (last[1::2]-last[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)
UPDATE: and a version that works with general (not just 0/1) entries:
def sum_stretches(a, axis=-1):
a = a.swapaxes(-1, axis)
dtype = np.result_type(a, 'i1')
bnd = np.diff((a!=0).astype(dtype), axis=-1, prepend=0, append=0)
*idx, last = np.where(bnd)
A = np.concatenate([np.zeros((*a.shape[:-1], 1), a.dtype), a.cumsum(axis=-1)], -1)[(*idx, last)]
bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)
Using itertools.groupby:
import itertools
for i in range(b.shape[1]):
counts = []
for k,v in itertools.groupby(b[:,i]):
g = list(v)
counts.extend([sum(g)] * len(g))
b[:,i] = counts
Output:
array([[3, 6, 1, 6, 0, 1],
[3, 6, 0, 6, 5, 0],
[3, 6, 3, 6, 5, 2],
[0, 6, 3, 6, 5, 2],
[2, 6, 3, 6, 5, 0],
[2, 6, 0, 6, 5, 1]])
building on paulpanzer's answer for poor souls (like me) who dont have numpy v1.15+
def sum_stretches(a, axis=-1):
a = a.swapaxes(-1, axis)
padding = [[0,0].copy()]*a.ndim
padding[-1] = [1,1]
padded = np.pad((a!=0), padding, 'constant', constant_values=0).astype('int32')
bnd = np.diff(padded, axis=-1)
*idx, last = np.where(bnd)
A = np.concatenate([np.zeros((*a.shape[:-1], 1), 'int32'), a.cumsum(axis=-1)], -1)[(*idx, last)]
bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)

Numpy, counting unique neighbours in 2d array

I am trying to count the number of neighbours for each element in a 2d numpy array that differ from the element itself (4-neighbourhood in this case, but 8-neighbourhood is also interesting).
Something like this:
input labels:
[[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 3 3 3 5 5]
[4 4 4 3 3 5 5]
[4 4 4 3 3 5 5]] (6, 7)
count of unique neighbour labels:
[[0 0 1 1 0 0 0]
[0 0 1 1 0 0 0]
[0 0 2 2 1 1 1]
[1 2 2 1 2 2 1]
[1 1 1 1 1 1 0]
[0 0 1 1 1 1 0]] (6, 7)
I have the code below, and out of curiosity I am wondering if there is a better way to achieve this, perhaps without the for loops?
import numpy as np
import cv2
labels_image = np.array([
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,3,3,3,5,5],
[4,4,4,3,3,5,5],
[4,4,4,3,3,5,5]])
print('input labels:\n', labels_image, labels_image.shape)
# Make a border, otherwise neighbours are counted as wrapped values from the other side
labels_image = cv2.copyMakeBorder(labels_image, 1, 1, 1, 1, cv2.BORDER_REPLICATE)
offsets = [(-1, 0), (0, -1), (0, 1), (1, 0)] # 4 neighbourhood
# Stack labels_image with one shifted per offset so we get a 3d array
# where each z-value corresponds to one of the neighbours
stacked = np.dstack(np.roll(np.roll(labels_image, i, axis=0), j, axis=1) for i, j in offsets)
# count number of unique neighbours, also take the border away again
labels_image = np.array([[(len(np.unique(stacked[i,j])) - 1)
for j in range(1, labels_image.shape[1] - 1)]
for i in range(1, labels_image.shape[0] - 1)])
print('count of unique neighbour labels:\n', labels_image, labels_image.shape)
I tried using np.unique with the return_counts and axis arguments, but could not get it to work.
Here's one approach -
import itertools
def count_nunique_neighbors(ar):
a = np.pad(ar, (1,1), mode='reflect')
c = a[1:-1,1:-1]
top = a[:-2,1:-1]
bottom = a[2:,1:-1]
left = a[1:-1,:-2]
right = a[1:-1,2:]
ineq = [top!= c,bottom!= c, left!= c, right!= c]
count = ineq[0].astype(int) + ineq[1] + ineq[2] + ineq[3]
blck = [top, bottom, left, right]
for i,j in list(itertools.combinations(range(4), r=2)):
count -= ((blck[i] == blck[j]) & ineq[j])
return count
Sample run -
In [22]: a
Out[22]:
array([[1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 2, 2, 2, 2],
[1, 1, 3, 3, 3, 5, 5],
[4, 4, 4, 3, 3, 5, 5],
[4, 4, 4, 3, 3, 5, 5]])
In [23]: count_nunique_neighbors(a)
Out[23]:
array([[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 2, 2, 1, 1, 1],
[1, 2, 2, 1, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 1, 0]])

Categories