finding neighbors in np.array at predefined start points - python

in an array i have 2 randomly defind start points for 2 agents that will fill the grid with "1" & "2" (that will get data from a pandas df)
#starting points:
start_point = []
cellMAP_0 = np.full((11,7), 0)
for i in range(2):
start_points = random.randint(0,cellMAP_0.size)
start_point.append(start_points)
further i assign each number to one agent:
for i in range(len(df[0:77])):
if df.loc[i]['side'] == 'MOVE' and df.loc[i]['symbol'] == 'FIGHTER_1':
cellMAP_0[np.unravel_index(random.choices(list(start_point)), cellMAP_0.shape)] = 1
elif df.loc[i]['side'] == 'MOVE' and df.loc[i]['symbol'] == 'FIGHTER_2':
cellMAP_0[np.unravel_index(random.choices(list(start_point)), cellMAP_0.shape)] = 2
this is my outcome:
[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 2 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 1]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]
so far so good. now i want to find horizontal & vertical neighbors for "1" and "2"
therefore i found this code, but somehow i don't get it to work with both instances (1&2) at once in the same array:
edit: sorry was a bit on the run here the code that i edited:
def find_horizontal_and_vertical_neighbours(cellMAP_0, start_point):
"""
This method takes 2d array and return list of all elements
with all horizontal and vertical neighbours
:param arr: 2d array
:return: list of array elements with neighbours
"""
neighbors = []
for start_point in range(len(cellMAP_0)):
for j, value in enumerate(cellMAP_0[start_point]):
if start_point == 0 or start_point == len(cellMAP_0) - 1 or j == 0 or j == len(cellMAP_0[start_point]) - 1:
# corners
new_neighbors = []
if start_point != 0:
new_neighbors.append(cellMAP_0[start_point - 1][j]) # top neighbor
if j != len(cellMAP_0[start_point]) - 1:
new_neighbors.append(cellMAP_0[start_point][j + 1]) # right neighbor
if start_point != len(cellMAP_0) - 1:
new_neighbors.append(cellMAP_0[start_point + 1][j]) # bottom neighbor
if j != 0:
new_neighbors.append(cellMAP_0[start_point][j - 1]) # left neighbor
else:
# add neighbors
new_neighbors = [
cellMAP_0[start_point - 1][j], # top neighbor
cellMAP_0[start_point][j + 1], # right neighbor
cellMAP_0[start_point + 1][j], # bottom neighbor
cellMAP_0[start_point][j - 1] # left neighbor
]
neighbors.append({
"index": start_point * len(cellMAP_0[start_point]) + j,
"value": value,
"neighbors": new_neighbors})
return neighbors
find_horizontal_and_vertical_neighbours(cellMAP_0,start_point)
this gives me inedex values and neighbors
[{'index': 0, 'value': 0, 'neighbors': [0, 0]},
{'index': 1, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 2, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 3, 'value': 0, 'neighbors': [0, 0, 0]},
{'index': 4, 'value': 0, 'neighbors': [0, 0, 0]},
but what i actually seek is a list of the cells next to the starting points (here the fat numbers in the array):
[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 **2** 0 0 0]
[0 0 **2** 2 **2** 0 0]
[0 0 0 **2** 0 0 0]
[0 0 0 0 0 0 **1**]
[0 0 0 0 0 **1** 1]
[0 0 0 0 0 0 **1**]
afterwards i want that the fighter can fill one field of the neighbours in each round, as well as that each new acquired field can act as a new starting field. the first seems kind of obvious for me to pick random positions from the list of neighbours i would get from the outcome above.
hope this is now a bit more clear :) thank you very much!

Since you want only the cells next to the starting points, you can use np.where() to find the indices of the starting points and then apply a function that finds all cells next to them:
oneIndex = np.where(cellMAP_0 == 1) #in your example it will return [8][6]
twoIndex = np.where(cellMAP_0 == 2) #in your example it will return [5][4]
def allInRange(num1,num2):
inRange = []
if num1< cellMAP_0.shape[0]-1:
inRange.append((num1+1,num2))
if num1>0:
inRange.append((num1-1,num2))
if num2< cellMAP_0.shape[1]-1:
inRange.append((num1,num2+1))
if num2>0:
inRange.append((num1,num2-1))
return inRange
firstNeighbours = allInRange(oneIndex[0],oneIndex[1])
secondNeighbours = allInRange(twoIndex[0],twoIndex[1])

Related

Fill the secondary diagonal with respect to a point?

The problem is that I have a point, say P = (p1,p2), in a 2x2 numpy array in Python. Now using the point P I want to fill the all the entries in the secondary diagonal passing through with that point.
So what it looks like is:
arr = [0,0,0,0,0
0,0,0,0,0
0,0,0,0,0
0,0,0,0,0
0,0,0,0,0]
P = (1,4)
arr = [0,0,0,0,0
0,0,0,0,1
0,0,0,1,0
0,0,1,0,0
0,1,0,0,0]
or let's say P = (3,0):
arr = [0,0,0,1,0
0,0,1,0,0
0,1,0,0,0
1,0,0,0,0
0,0,0,0,0]
The array with ones is the final result required.
You can slice the array using the indices and feed it to np.fliplr and p.fill_diagonal to get the reversed diagonal
arr = np.zeros(shape=(5, 5), dtype=int)
p = (...)
np.fill_diagonal(np.fliplr(arr[p[0]:, :p[1]+1]), 1)
print(arr)
Output
p = 1, 4
[[0 0 0 0 0]
[0 0 0 0 1]
[0 0 0 1 0]
[0 0 1 0 0]
[0 1 0 0 0]]
p = 0, 3
[[0 0 0 1 0]
[0 0 1 0 0]
[0 1 0 0 0]
[1 0 0 0 0]
[0 0 0 0 0]]

How to resize a Numpy array to add/replace rows with combinations determined by the values in each row of the array

So I have a program that at some point creates random arrays and I have perform an operation which is to add rows while replacing other rows based on the values found in the rows. One of the random arrays will look something like this but keep in mind that it could randomly vary in size ranging from 3x3 up to 10x10:
0 2 0 1
1 0 0 1
1 0 2 1
2 0 1 2
For every row that has at least one value equal to 2 I need to remove/replace the row and add some more rows. The number of rows added will depend on the number of combinations possible of 0s and 1s where the number of digits is equal to the number of 2s counted in each row. Each added row will introduce one of these combinations in the positions where the 2s are located. The result that I'm looking for will look like this:
0 1 0 1 # First combination to replace 0 2 0 1
0 0 0 1 # Second combination to replace 0 2 0 1 (Only 2 combinations, only one 2)
1 0 0 1 # Stays the same
1 0 1 1 # First combination to replace 1 0 2 1
1 0 0 1 # Second combination to replace 1 0 2 1 (Only 2 combinations, only one 2)
0 0 1 0 # First combination to replace 2 0 1 2
0 0 1 1 # Second combination to replace 2 0 1 2
1 0 1 1 # Third combination to replace 2 0 1 2
1 0 1 0 # Fourth combination to replace 2 0 1 2 (4 combinations, there are two 2s)
If you know a Numpy way of accomplishing this I will be grateful.
You can try the following. Create a sample array:
import numpy as np
np.random.seed(5)
a = np.random.randint(0, 3, (4, 4))
print(a)
This gives:
[[2 1 2 2]
[0 1 0 0]
[2 0 2 0]
[0 1 1 0]]
Compute the output array:
ts = (a == 2).sum(axis=1)
r = np.hstack([np.array(np.meshgrid(*[[0, 1]] * t)).reshape(t, -1).T.ravel() for t in ts if t])
out = np.repeat(a, 2**ts, axis=0)
out[out == 2] = r
print(out)
Result:
[[0 1 0 0]
[0 1 0 1]
[1 1 0 0]
[1 1 0 1]
[0 1 1 0]
[0 1 1 1]
[1 1 1 0]
[1 1 1 1]
[0 1 0 0]
[0 0 0 0]
[1 0 0 0]
[0 0 1 0]
[1 0 1 0]
[0 1 1 0]]
Not the prettiest code but it does the job. You could clean up the itertools calls but this lets you see how it works.
import numpy as np
import itertools
X = np.array([[0, 2, 0, 1],
[1, 0, 0, 1],
[1, 0, 2, 1],
[2, 0, 1, 2]])
def add(X_,Y):
if Y.size == 0:
Y = X_
else:
Y = np.vstack((Y, X_))
return(Y)
Y = np.array([])
for i in range(len(X)):
if 2 not in X[i,:]:
Y = add(X[i,:], Y)
else:
a = np.where(X[i,:]==2)[0]
n = [[i for i in itertools.chain([1, 0])] for _ in range(len(a))]
m = list(itertools.product(*n))
for j in range(len(m)):
M = 1 * X[i,:]
u = list(m[j])
for k in range(len(a)):
M[a[k]] = u[k]
Y = add(M, Y)
print(Y)
#[[0 1 0 1]
# [0 0 0 1]
# [1 0 0 1]
# [1 0 1 1]
# [1 0 0 1]
# [1 0 1 1]
# [1 0 1 0]
# [0 0 1 1]
# [0 0 1 0]]

Write functions resilient to variable dimension array

I'm struggling when writing a function that would seemlessly apply to any numpy arrays whatever its dimension.
At one point in my code, I have boolean arrays that I consider as mask for other arrays (0 = not passing, 1 = passing).
I would like to "enlarge" those mask arrays by overriding zeros adjacent to ones on a defined range.
Example :
input = [0,0,0,0,0,1,0,0,0,0,1,0,0,0]
enlarged_by_1 = [0,0,0,0,1,1,1,0,0,1,1,1,0,0]
enlarged_by_2 = [0,0,0,1,1,1,1,1,1,1,1,1,1,0]
input = [[0,0,0,1,0,0,1,0],
[0,1,0,0,0,0,0,0],
[0,0,0,0,0,0,1,0]]
enlarged_by_1 = [[0,0,1,1,1,1,1,1],
[1,1,1,0,0,0,0,0],
[0,0,0,0,0,1,1,1]]
This is pretty straighforward when inputs are 1D.
However, I would like this function to take seemlessy 1D, matrix, 3D, and so on.
So for a matrix, the same logic would be applied to each lines.
I read about ellipsis, but it does not seem to be applicable in my case.
Flattening the input applying the logic and reshaping the array would lead to possible contamination between individual arrays.
I do not want to go through testing the shape of input numpy array / recursive function as it does not seems very clean to me.
Would you have some suggestions ?
The operation that you are described seems very much like a convolution operation followed by clipping to ensure that values remain 0 or 1.
For your example input:
import numpy as np
input = np.array([0,0,0,0,0,1,0,0,0,0,1,0,0,0], dtype=int)
print(input)
def enlarge_ones(x, k):
mask = np.ones(2*k+1, dtype=int)
return np.clip(np.convolve(x, mask, mode='same'), 0, 1).astype(int)
print(enlarge_ones(input, k=1))
print(enlarge_ones(input, k=3))
which yields
[0 0 0 0 0 1 0 0 0 0 1 0 0 0]
[0 0 0 0 1 1 1 0 0 1 1 1 0 0]
[0 0 1 1 1 1 1 1 1 1 1 1 1 1]
numpy.convolve only works for 1-d arrays. However, one can imagine a for loop over the number of array dimensions and another for loop over each array. In other words, for a 2-d matrix first operate on every row and then on every column. You get the idea for nd-array with more dimensions. In other words the enlarge_ones would become something like:
def enlarge_ones(x, k):
n = len(x.shape)
if n == 1:
mask = np.ones(2*k+1, dtype=int)
return np.clip(np.convolve(x, mask, mode='same')[:len(x)], 0, 1).astype(int)
else:
x = x.copy()
for d in range(n):
for i in np.ndindex(x.shape[:-1]):
x[i] = enlarge_ones(x[i], k) # x[i] is 1-d
x = x.transpose(list(range(1, n)) + [0])
return x
Note the use of np.transpose to rotate the dimensions so that np.convolve is applied to the 1-d along each dimension. This is exactly n times, which returns the matrix to original shape at the end.
x = np.zeros((3, 5, 7), dtype=int)
x[1, 2, 2] = 1
print(x)
print(enlarge_ones(x, k=1))
[[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]
[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 1 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]
[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]]
[[[0 0 0 0 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 0 0 0 0 0 0]]
[[0 0 0 0 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 0 0 0 0 0 0]]
[[0 0 0 0 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 1 1 1 0 0 0]
[0 0 0 0 0 0 0]]]

Best way to go towards an index in numpy, with wrap

Lets say I have a 2D array below:
[[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 0 ]
[ 0 0 0 0 0 2 ]
[ 0 1 0 0 0 0 ]
[ 0 0 0 0 0 0 ]]
I would like to get the direction from where '1' (index 4,1) is to '2' (index 3,5). Assuming directions are only up, down, left, right. Thus no diagonal movement.
One way to get the directions:
"right" if destination.x > start.x else "left" if target.x < start.x else None
"down" if destination.y > start.y else "up" if destination.y < start.y else None
So for this example, we can go to '2' or the destination by either going "up" or "right". That of course is just one step, once you moved, can perform the same logic to move closer to the destination.
The problem with this logic is that it doesnt take the wrapping into account. With this logic it will take 5 steps to get to the destination. There is a shorter way by actually going left or up that can get to the destination in just 3 steps, because of the wrap.
Was thinking of generating another array where the start will be the middle of the array and perform the same logic. The problem is if the array is even (like this is 6x6, need to pad to get a middle. For example:
[[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 2 0 0 0 0 0]
[ 0 0 0 1 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]]
Here the array is now 7x7. I believe there is a simplier way to get the answer without this extra step, but cant think of it.
Can you consider using this method?
import numpy as np
# build the array
a = np.zeros( (6,6), dtype=int )
a[4][1] = 1
a[3][5] = 2
# extract required informations
i,j = np.where(a == 1)
h,k =np.where(a == 2)
print (i-h) => [1]
print (j-k) => [-4]
Well, there is a quite simple formula for computing the distance in case of periodic boundary conditions. Below I consider only periodic b.c. on x-axis:
import numpy as np
# periodic boundary condition for the x-axis only
def steps(start, dest, L_x):
x_start = start[1]
y_start = start[0]
x_dest = dest[1]
y_dest = dest[0]
dx = x_dest - x_start
if np.abs(dx) <= L_x/2:
steps_x = x_dest - x_start
else:
if dx > 0:
steps_x = (x_dest - L_x) - x_start
else:
steps_x = (x_dest + L_x) - x_start
steps_y = y_dest - y_start
return steps_x, steps_y
Example:
grid = np.array([[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 2 ],
[0, 1, 0, 0, 0, 0 ],
[0, 0, 0, 0, 0, 0 ]])
L_x = grid.shape[1]
start = (4, 1) # (y, x) or (i, j)
dest = (3, 5)
steps_x, steps_y = steps(start, dest, grid)
dir_x = 'left' if steps_x < 0 else 'right'
dir_y = 'up' if steps_y < 0 else 'down'
print(abs(steps_x), dir_x, ',', abs(steps_y), dir_y)
Out: 2 left , 1 up
I try an other way:
On an horizontal axis of length size, to go from a to b, let delta = ((b-a)%size*2-1)//size.
if delta=-1, a=b : you don't move.
if delta=0 : you have to go right.
if delta=1 : you have to go left.
So this code seems works
size=10
vertical=['down','up',None]
horizontal=['right','left',None]
def side(a,b):
return ((b-a)%size*2-1)//size
def step(M1,M2):
x1,y1=M1
x2,y2=M2
return (vertical[side(x1,x2)],horizontal[side(y1,y2)])
For example :
In [6]: step((2,1),(2,8))
Out[6]: (None, 'left')

Multiple Numpy random shuffles does not add up

I'm trying to generate an array of neighbors, each neighbor differing from the previous neighbor in one random shuffle, either a row or column shuffle. The neighbors should only be generated if a certain row or column has more than K 1's, and if so, a neighbor should be generated with this row or column shuffled. The shuffles should be additive, meaning that if first a row is shuffled and then a column; two neighbors should be generated, one with a shuffled row and one with both a row and a column shuffled.
However, it seems that the array is only shuffled two times, one for the column loop and one for the row loop, and the shuffles don't add up.
In this example, column index 1 and 2 should be shuffled, and row index 3 and 4. Here is an example output, showing the generated neighbors:.
[[0 0 0 1 1]
[0 0 1 0 0]
[1 1 0 0 0]
[0 1 1 1 0]
[1 1 1 0 1]]
[[0 0 0 1 1]
[0 0 1 0 0]
[0 0 1 0 1]
[0 1 1 1 0]
[1 1 1 0 1]]
[[0 0 0 1 1]
[0 0 1 0 0]
[0 0 1 0 1]
[0 1 1 1 0]
[1 1 1 0 1]]
[[0 0 0 1 1]
[0 0 1 0 0]
[0 0 1 0 1]
[0 1 1 1 0]
Here is the code:
k=2
tmp = np.array([
[0, 0, 0, 1, 1]
,[0, 0, 1, 0, 0]
,[1, 1, 0, 0, 0]
,[0, 1, 1, 1, 0]
,[1, 1, 1, 0, 1]
])
board = np.copy(tmp)
column = np.sum(board, axis=0)-k
columns = len(column)
row = np.sum(board, axis=1)-k
rows = len(row)
neighbors = []
for i in range(columns):
if column[i] > 0:
np.random.shuffle(board[:,i])
neighbors.append(np.copy(board))
for i in range(rows):
if row[i] > 0:
np.random.shuffle(board[i,:])
neighbors.append(np.copy(board))
print tmp
print column
print row
for i in neighbors:
print i
Thanks
Your code works :)
It's just that sometimes a row or column is shuffled but stays in the same configuration. Compare with:
ar = np.array([0, 1])
for _ in range(10):
print ar
np.random.shuffle(ar)
In addition, it's not unlikely that the elements of a row or column are indeed shuffled, but you'll end up with ones and zeros in the exact same place anyway (because there's only a limited number of unique permutations).

Categories