Searching values in matrix in python - python

I am trying to resolve one task with matrix. I have function: fill_area(matrix, coordinates, value). If I would input coordinates for example (1,0) and value 3, it should write 3 in position [1][0] and rewrite the value to 3. If near numbers have the same value, it should also rewrite them. I would like to use an stack or queue for numbers, which have to change an value.
But I have no idea, how to check all nearest position.
matrix = [[2, 0, 1],
[0, 0, 1],
[0, 1, 1]]
fill_area(matrix, (1, 0), 3)
matrix = [[2, 0, 1],
[3, 3, 1],
[3, 1, 1]]

The preferred way to handle matrices is numpy but there seems to be no obvious way to handle this special case while avoiding loops over items in numpy. I take it from your example that you define "nearness" as the patch around the given coordinates but in coordinate-direction only. I have two versions, one for the entire coordinate-patch around the given coordinates, the other in ij-direction only (per your example). Both are list comprehensions, for better readability you might want to change that to regular loops.
def fill_area(mat, ij, val):
'''set patch around ij in mat to val for all positions where the original mat value == mat val at ij, full patch'''
return [[val if abs(i-ij[0])<2 and abs(j-ij[1])<2 and mat[i][j] == mat[ij[0]][ij[1]]
else mat[i][j] for j in range(len(mat[1]))] for i in range(len(mat))]
def fill_area1(mat, ij, val):
'''set patch around ij in mat to val for all positions where the original mat value == mat val at ij, patch ij-dir'''
return [[val if ((abs(i-ij[0])<2 and j==ij[1]) or (abs(j-ij[1])<2 and i==ij[0])) and mat[i][j] == mat[ij[0]][ij[1]]
else mat[i][j] for j in range(len(mat[1]))] for i in range(len(mat))]
matrix = [[2, 0, 1],
[0, 0, 1],
[0, 1, 1]]
print('entire patch: ', fill_area(matrix, (1, 0), 3))
print('ij-directional: ', fill_area1(matrix, (1, 0), 3))
which produces
entire patch: [[2, 3, 1], [3, 3, 1], [3, 1, 1]]
ij-directional: [[2, 0, 1], [3, 3, 1], [3, 1, 1]]

Related

Diagonal array in numpy

If I have the array [[1,0,0],[0,1,0],[0,0,1]] (let's call it So) which is done as numpy.eye(3).
How can I get that the elements below the diagonal are only 2 and 3 like this [[1,0,0],[2,1,0],[3,2,1]] ?? How can I assign vectors of an array to a different set of values?
I know I could use numpy.concatenate to join 3 vectors and I know how to change rows/columns but I can't figure out how to change diagonals below the main diagonal.
I tried to do np.diagonal(So,-1)=2*np.diagonal(So,-1) to change the diagonal right below the main diagonal but I get the error message cannot assign to function call.
I would not start from numpy.eye but rather numpy.ones and use numpy.tril+cumsum to compute the next numbers on the lower triangle:
import numpy as np
np.tril(np.ones((3,3))).cumsum(axis=0).astype(int)
output:
array([[1, 0, 0],
[2, 1, 0],
[3, 2, 1]])
reversed output (from comment)
Assuming the array is square
n = 3
a = np.tril(np.ones((n,n)))
(a*(n+2)-np.eye(n)*n-a.cumsum(axis=0)).astype(int)
Output:
array([[1, 0, 0],
[3, 1, 0],
[2, 3, 1]])
Output for n=5:
array([[1, 0, 0, 0, 0],
[5, 1, 0, 0, 0],
[4, 5, 1, 0, 0],
[3, 4, 5, 1, 0],
[2, 3, 4, 5, 1]])
You can use np.fill_diagonal and index the matrix so the principal diagonal of your matrix is the one you want. This suposing you want to put other values than 2 and 3 is the a good solution:
import numpy as np
q = np.eye(3)
#if you want the first diagonal below the principal
# you can call q[1:,:] (this is not a 3x3 or 2x3 matrix but it'll work)
val =2
np.fill_diagonal(q[1:,:], val)
#note that here you can use an unique value 'val' or
# an array with values of corresponding size
#np.fill_diagonal(q[1:,:], [2, 2])
#then you can do the same on the last one column
np.fill_diagonal(q[2:,:], 3)
You could follow this approach:
def func(n):
... return np.array([np.array(list(range(i, 0, -1)) + [0,] * (n - i)) for i in range(1, n + 1)])
func(3)
OUTPUT
array([[1, 0, 0],
[2, 1, 0],
[3, 2, 1]])

Looking to iterate through list of lists that skips over specific indices

I'm currently working on a Python 3 project that involves iterating several times through a list of lists, and I want to write a code that skips over specific indices of this list of lists. The specific indices are stored in a separate list of lists. I have written a small list of lists, grid and the values I do not want to iterate over, coordinates:
grid = [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
coordinates = [[0, 2], [1, 1], [2, 0]]
Basically, I wish to skip over each 1 in grid(the 1s are just used to make the corresponding coordinate locations more visible).
I tried the following code to no avail:
for row in grid:
for value in row:
for coordinate in coordinates:
if coordinate[0] != grid.index(row) and coordinate[1] != row.index(value):
row[value] += 4
print(grid)
The expected output is: [[4, 4, 1], [4, 1, 4], [1, 4, 4]]
After executing the code with, I am greeted with ValueError: 1 is not in list.
I have 2 questions:
Why am I being given this error message when each coordinate in coordinates contains a 0th and 1st position?
Is there a better way to solve this problem than using for loops?
There are two issues with your code.
The row contains a list of integers, and value contains values in those rows. The issue is that you need access to the indices of these values, not the values themselves. The way that you've set up your loops don't allow for that.
.index() returns the index of the first instance of the argument passed in; it is not a drop-in replacement for using indexing with brackets.
Here is a code snippet that does what you've described, fixing both of the above issues:
grid = [[0, 0, 1], [0, 1, 0], [1, 0, 0]]
coordinates = [[0, 2], [1, 1], [2, 0]]
for row in range(len(grid)):
for col in range(len(grid[row])):
if [row, col] not in coordinates:
grid[row][col] += 4
print(grid) # -> [[4, 4, 1], [4, 1, 4], [1, 4, 4]]
By the way, if you have a lot of coordinates, you can make it a set of tuples rather than a 2D list so you don't have to iterate over the entire list for each row / column index pair. The set would look like coordinates = {(0, 2), (1, 1), (2, 0)}, and you'd use if (row, col) not in coordinates: as opposed to if [row, col] not in coordinates: if you were using a set instead.
Here is a numpy way to do this, for anyone looking -
import numpy as np
g = np.array(grid)
c = np.array(coordinates)
mask = np.ones(g.shape, bool)
mask[tuple(c.T)] = False
#This mask skips each coordinate in the list
g[mask]+=4
print(g)
[[4 4 1]
[4 1 4]
[1 4 4]]
And a one-liner list comprehension, for those who prefer that -
[[j+4 if [row,col] not in coordinates else j for col,j in enumerate(i)] for row,i in enumerate(grid)]
[[4, 4, 1], [4, 1, 4], [1, 4, 4]]
grid = [
[0, 0, 1],
[0, 1, 0],
[1, 0, 0]]
coordinates = [[0, 2], [1, 1], [2, 0]]
for y, row in enumerate(grid):
for x, value in enumerate(row):
if [x, y] in coordinates or value != 1:
grid[y][x] += 4
print(grid)

Having trouble with numpy append function

I was doing work for a course today, and the assignment was to create a tic tac toe board. The possibilities method takes a tic tac toe board as an input, and checks if any of the values are "0", meaning that it's an open space. My plan would be to add the location of the 0 to an array, called locations, and then return locations at the end of the function. However, when I try to append the location of the 0 to the locations array, I keep getting this issue: "all the input array dimensions for the concatenation axis must match exactly, but along dimension 0, the array at index 0 has size 2 and the array at index 1 has size 1". Does anyone know how to solve this? Thanks
import numpy as np
def create_board():
board = np.zeros((3,3), dtype = "int")
return board
def place(board, player, position):
x, y = position
board[x][y] = player
def posibilities(board):
locations = np.empty(shape=[2,0])
for i in range(len(board)):
for x in range(len(board[0])):
if board[i][x] == 0:
locations = np.append(locations, [[i,x]], axis=1)
print(locations)
posibilities(create_board())
As #hpaulj suggested use list instead and change it to np.array at the end, that is:
def posibilities(board):
locations = []
for i in range(len(board)):
for x in range(len(board[0])):
if board[i][x] == 0:
locations.append([[i,x]])
locations = np.array(locations) # or np.concatenate(locations) depending what you want
print(locations)
This is the proper way to do this due to the fact that python lists are mutable and numpy arrays aren't.
In [530]: board = np.random.randint(0,2,(3,3))
In [531]: board
Out[531]:
array([[0, 0, 0],
[1, 0, 1],
[0, 1, 0]])
Looks like you are trying to collect the locations on the board where it is 0. argwhere does this nicely:
In [532]: np.argwhere(board==0)
Out[532]:
array([[0, 0],
[0, 1],
[0, 2],
[1, 1],
[2, 0],
[2, 2]])
With the list append:
In [533]: alist = []
In [534]: for i in range(3):
...: for j in range(3):
...: if board[i,j]==0:
...: alist.append([i,j])
...:
In [535]: alist
Out[535]: [[0, 0], [0, 1], [0, 2], [1, 1], [2, 0], [2, 2]]
argwhere actually uses np.nonzero to get a tuple of arrays which index the desired locations.
In [536]: np.nonzero(board==0)
Out[536]: (array([0, 0, 0, 1, 2, 2]), array([0, 1, 2, 1, 0, 2]))
Often this nonzero version is easier to use. For example it can be used directly to select all those cells:
In [537]: board[np.nonzero(board==0)]
Out[537]: array([0, 0, 0, 0, 0, 0])
and setting some of those to 1:
In [538]: board[np.nonzero(board==0)] = np.random.randint(0,2,6)
In [539]: board
Out[539]:
array([[0, 0, 1],
[1, 0, 1],
[1, 1, 1]])

Create a matrix from a function

I want to create a matrix from a function, such that the (3,3) matrix C has values equal to 1 if the row index is smaller than a given threshold k.
import numpy as np
k = 3
C = np.fromfunction(lambda i,j: 1 if i < k else 0, (3,3))
However, this piece of code throws an error
"The truth value of an array with more than one element is ambiguous.
Use a.any() or a.all()" and I do not really understand why.
The code for fromfunction is:
dtype = kwargs.pop('dtype', float)
args = indices(shape, dtype=dtype)
return function(*args, **kwargs)
You see it calls function just once - with the whole array of indices. It is not iterative.
In [672]: idx = np.indices((3,3))
In [673]: idx
Out[673]:
array([[[0, 0, 0],
[1, 1, 1],
[2, 2, 2]],
[[0, 1, 2],
[0, 1, 2],
[0, 1, 2]]])
Your lambda expects scalar i,j values, not a 3d array
lambda i,j: 1 if i < k else 0
idx<3 is a 3d boolean array. The error arises when that is use in an if context.
np.vectorize or np.frompyfunc is better if you want to apply a scalar function to a set of arrays:
In [677]: np.vectorize(lambda i,j: 1 if i < 2 else 0)(idx[0],idx[1])
Out[677]:
array([[1, 1, 1],
[1, 1, 1],
[0, 0, 0]])
However it isn't faster than more direct iterative approaches, and way slower than functions that operation on whole arrays.
One of many whole-array approaches:
In [680]: np.where(np.arange(3)[:,None]<2, np.ones((3,3),int), np.zeros((3,3),int))
Out[680]:
array([[1, 1, 1],
[1, 1, 1],
[0, 0, 0]])
As suggested by #MarkSetchell you need to vectorize your function:
k = 3
f = lambda i,j: 1 if i < k else 0
C = np.fromfunction(np.vectorize(f), (3,3))
and you get:
C
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]])
The problem is that np.fromfunction does not iterate over all elements, it only returns the indices in each dimension. You can use np.where() to apply a condition based on those indices, choosing from two alternatives depending on the condition:
import numpy as np
k = 3
np.fromfunction(lambda i, j: np.where(i < k, 1, 0), (5,3))
which gives:
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[0, 0, 0],
[0, 0, 0]])
This avoids naming the lambda without things becoming too unwieldy. On my laptop, this approach was about 20 times faster than np.vectorize().

Render visible voxel in 3D array without iterate it all

I'm doing a voxel engine and my data are in a 3D array per chunk. To render voxels, I need to know which one can be visible, so they will have Air block around it (0 in my data structure).
So I need to find all number which have a 0 beside in left, right, front, back, top and bot direction, but I really don't know how to do it.
I know I can do it with loop, but my goal is to achieve this task the fastest as I can.
For exemple (in 2D):
[[0, 0, 0, 0, 0,],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 1],
[0, 1, 1, 1, 0]]
Must return me these indexes:
(1,1) (1,2) (1,3)
(2,1) (2,3)
(3,1) (3,4)
(4,1) (4,2) (4,3)
I want to have the index of the numbers which are at the edge of the array even if there is no 0 around, because I can then check in beside chunk if there is Air data.
I don't know if it is possible, and if it is not, how can I render visible voxel without iterate over all the array?
Thanks.
(Sorry for my english, send comment if you don't understand all)
EDIT
After reading #Mad_Physicist comment, I post my uncomplete method to achieve the task with a loop (I didn't finish it because I was searching for a fastest way instead):
matrix = numpy.zeros((10,10,10), dtype="uint8")
# Filling with some random data
for y in range(10):
for z in range(10):
for x in range(10):
matrix[y, z, x] = random.choice((0,1,1)
result = numpy.transpose(numpy.where(matrix == 0))
for num in result.tolist():
u = matrix.item(*num)
# TODO: Check each direction
The weakness of this method is that I sometime pass throught useless data (0 with 0 in all directions).
You could use scipy.ndimage.morphology.binary_erosion:
from scipy.ndimage.morphology import binary_erosion
np.argwhere(arr & ~binary_erosion(arr))
Output:
array([[1, 1],
[1, 2],
[1, 3],
[2, 1],
[2, 3],
[3, 1],
[3, 4],
[4, 1],
[4, 2],
[4, 3]])

Categories

Resources