Python: Directional Navigation in 2d Array - python

TLDR: I want to navigate all directions in a 2D array starting at the center
I am trying to do a particular navigation in a 2D Python array. Say I have this 2D array (A1):
I want to navigate through this array and apply the summation of the index for A1 * index of A2 where A2 is another 2D array (A2):
When applying the summation, I want the current index, to start with the center of the second 2D array. So at A1[0][0] (1) I want to use the center of A2 (A21, or 0). I then want to move through each item in the second array and continue this process:
1(0) + 2(1) + 5(2) + 6(1)
This will take care of all the items from A2[center x][center y]. I then want to double back, and add all the values that are below the center. Here is a quick image illustrating what I want to do, one showing a case where we are out of bounds, and on, in bounds:
What I am trying to do is write a function that will navigate, from the center of a 2D array, out in each direction of that 2D array. Here is the code I have for it:
for row in range(A1.length):
for column in range(A1[0].length):
cRow = 0
pSum = 0
kRow = midRow - 1
while kRow < A2.length and row + kRow < A1.length:
kColumn = midColumn - 1
cColumn = 0
while kColumn < A2[0].length and column + kColumn < A1[0].length:
pSum += float(A1[row + cRow][column + cColumn] * A2[kRow][kColumn])
kColumn += 1
cColumn += 1
kRow += 1
cRow += 1
nKRow = midRow - 1
cRow = 0
#Get rows past
while nKRow >= 0 and row - nKRow >= 0:
nKColumn = midColumn - 1
cColumn = 0
while nKColumn >= 0 and column - nKColumn >= 0:
# Account for row 0
if row == 1 and cColumn == 0:
pSum += float(A1[0][column - cColumn] * A2[nKRow][nKColumn])
else:
pSum += float(A1[row - cRow][column - cColumn] * A2[nKRow][nKColumn])
nKColumn -= 1
cColumn += 1
nKRow -= 1
cRow += 1
value = float(pSum / average)
A3[row][column] = value
This works in most cases, but does not work correctly in edge cases. The problem is that I am having issues setting values when row = 0 or column = 0.
Is there a better approach to navigate every direction in a 2d array starting at the center?

It looks like you are trying to do kernel convolution, yes?
Where are midRow, midColumn, and average defined in your example?
Why do you have to start in the center of A2? Summation and multiplication are both commutative operations, so the order shouldn't matter. It would be much simpler to start in the top left corner of A2. Something like (I haven't tested this):
kernelSize = 3
kernelOffset = int(kernelSize / 2) # Assumes only odd-sized kernels
for x in xrange(len(A1)):
for y in xrange(len(A1[0])):
total = 0
for kx in xrange(kernelSize):
for ky in xrange(kernelSize):
offsetX = -kernelOffset + kx # Should give something in the range [-1, 1]
offsetY = -kernelOffset + ky
currentX = x + offsetX
currentY = y + offsetY
if currentX < 0 or currentX >= len(A1) or currentY < 0 or currentY >= len(A1[0]): # do nothing if "out of bounds"
continue
total += A1[currentX][currentY] * A2[kx][ky]
A3[x][y] = total

Related

How to create a matrix with moving elements?

I am trying to create a code for an 8x8 matrix that is full of zeros except[1,1] and [8,8]. These values would be equal to 1. I then want to make the 1's at each corner be able to move up, down, left, right, as long as it is within the matrix. I would like to be able to find out how many moves until the 1's cross each other.
I understand I need to make 2 arrays but really unsure how to code this.
You can use a for loop to create the matrix like this:
matrix = []
for x in range(8):
matrix.append([])
for y in range(8):
matrix[x].append(0)
Then you can change matrix[1][1], and matrix[8][8] to 1's.
However, I'm pretty sure you meant matrix[0][0] and matrix[7][7] because list indexing starts at [0].
matrix[0][0] = 1
matrix[7][7] = 1
To move the 1's across the matrix, you can create a function called move() and then remember the positions of the 1's in variables
posX = [0, 7]
posY = [0, 7]
#argument dirn, 0 = right, 1 = left, 2 = up, 3 = down
#argument one, gives which one you want to move
def move(dirn, one):
if dirn == 0 and posX[one] < 7:
matrix[posX[one]][posY[one]] = 0
matrix[posX[one] + 1][posY[one]] = 1
posX[one] = posX[one] + 1
if dirn == 1 and posX[one] > 0:
matrix[posX[one]][posY[one]] = 0
matrix[posX[one] - 1][posY[one]] = 1
posX[one] = posX[one] - 1
if dirn == 2 and posY[one] < 7:
matrix[posX[one]][posY[one]] = 0
matrix[posX[one]][posY[one] + 1] = 1
posY[one] = posY[one] + 1
if dirn == 3 and posY[one] > 0:
matrix[posX[one]][posY[one]] = 0
matrix[posX[one]][posY[one] - 1] = 1
posY[one] = posY[one] - 1
You can find out how many moves until the 1's cross each other, by adding 1 to a variable every time you make a move. Then check if the ones are crossing each other.
moves = 0
while not [posX[0], posY[0]] == [posX[1], posY[1]]:
#make a move
moves += 1
Total moves made will be the moves variable after the process has ended. Print the value of the moves variable after the while loop.
print(moves)

index 20 is out of bounds for axis 0 with size 20

I am trying to create a function that has an array of zeros with a 1 at the center. Using randrange, the function decides where to add a count in the array with respect to the center. The new site will then be the new reference rather than the center. This should happen for m iterations. Once the array has a 1 in the first site, then the function should return the m value in which this occurred.
def random_walk_mod(n,m):
array = np.zeros(n) #create array of zeros of length n
array[n//2] = 1
start_pos = n//2
for i in range(m):
direction = randrange(-1,2,2) #get direction of left or right
pos = start_pos + direction #Change position from center
array[pos] = array[pos]+1
start_pos = pos
if array[0] == 1:
return i
However when calling the function more than once with
for i in range(20):
m = random_walk_mod(20,1000):
print(m)
I get this error
index 20 is out of bounds for axis 0 with size 20
The issue come from this line.
pos = start_pos + direction
Your direction can be 1 or - 1, but if it's 10 time 1, as your exemple start with 20, you will get 20 // 2 + 10 = 20. As you have create an array with 20 value, index 20 does not exist.
Now you can catch it with a try/except block or you can add another condition to check if 1 is at the end of your array.
You need to place a stopper for the right side of the array to prevent to going out of range. This should work:
def random_walk_mod(n,m):
array = np.zeros(n) #create array of zeros of length n
array[n//2] = 1
start_pos = n//2
for i in range(m):
direction = random.randrange(-1,2,2) #get direction of left or right
pos = start_pos + direction #Change position from center
array[pos] = array[pos]+1
start_pos = pos
if array[0] == 1:
return i
if array[n-1] == 1: #This code will prevent your index for going to far on the right
return i

Function to find winning line with NxN board and M pieces in a row in Python 3

I am trying to create a function that finds if a move is winning on an NxN board where the winning condition is M pieces in a row in Python 3.
I am pretty new to programming and in my specific case I am creating a Gomoku game (15x15 board with 5 pieces in a row to win). To get it working I created 6 for loops to check vertical, horizontal and 4 diagonals. See the code below for examples on the 2 options for left to right digonals. This takes way too long though when I need to loop through it many times (8) for computer to find if I can win or if it has a winning move.
end_row = 15
for j in range(11):
end_row -= 1
counter = 0
for i in range(end_row):
if board[i+j][i] == board[i+1+j][i+1] and board[i+j][i] != ' ':
counter += 1
if counter == 4:
winning_line = [(i+j-3, i-3), (i+j-2, i-2), (i+j-1, i-1), (i+j, i), (i+1+j, i+1)]
winner = True
break
else:
counter = 0
# Top left to bottom right, lower side
end_row = 15
for j in range(11):
end_row -= 1
counter = 0
for i in range(end_row):
if board[i][i+j] == board[i+1][i+1+j] and board[i][i+j] != ' ':
counter += 1
if counter == 4:
winning_line = [(i-3, i+j-3), (i-2, i+j-2), (i-1, i+j-1), (i, i+j), (i+1, i+1+j)]
winner = True
break
else:
counter = 0
# What I want to do instead, where x and y are coordinates of last move:
# Horizontal
counter = 0
for i = x - (n - 1) to x + (n - 1):
if board[i][y] == board[x][y] :
counter++
else :
counter = 0
if counter == n:
return true
The problem with the lower part of the code is that if I place a piece on e.g. position (0, 0) the program will complain when trying to reach board[-4][0] in the first looping. I will have to place lots of if statements when I get close to the edge, which is not an elegant solution.
I thought of making a 3*15 x 3*15 board instead, where the actual board is the inner 15x15 part and the rest just contains placeholders:
15x15 || 15x15 || 15x15
15x15 || board || 15x15
15x15 || 15x15 || 15x15
This to avoid getting outside of my list of lists when looping through. Not an elegant solution either, but takes less space in the code.
Any suggestions on how to solve this problem? Thank you in advance from a beginner programmer!
As #MePsyDuck mentioned in comments, you can use min and max functions to limit the range to only reference valid squares in the board matrix.
Furthermore, you could make a generic function that does the count-job on any given list of values. Then you can call that generic function four times: once for every direction (horizontal, vertical, diagonal \ and diagonal /)
Here is how that could work:
def is_win(board, n, x, y):
end_row = len(board)
color = board[x][y]
def check(values):
counter = 0
for value in values:
if value == color:
counter += 1
else:
counter = 0
if counter == n:
return True
return False
return (check([board[i][y] for i in range(max(0, x - n + 1), min(end_row, x + n))])
or check([board[x][i] for i in range(max(0, y - n + 1), min(end_row, y + n))])
or check([board[x+i][y+i] for i in range(max(-x, -y, 1 - n), min(end_row - x, end_row - y, n))])
or check([board[x+i][y-i] for i in range(max(-x, y - end_row + 1, 1 - n), min(end_row - x, y + 1, n))]))
Instead of looping from 0 to 14, just loop from 0 to (board_size - winning_length).
Here's an example for a 1-dimensional board:
BOARD_SIZE = 15
WINNING_LENGTH = 5
for x in range(BOARD_SIZE - WINNING_LENGTH):
players_here = set()
for pos in range(x, x + WINNING_LENGTH):
players_here.add(board[pos])
if len(players_here) == 1:
# Exactly 1 player occupies every position in this line, so they win

How can it work the first time, but fail the second time?

In my code I did the same thing twice, but it works only the first time.
for y, row in enumerate(matrix):
for x, color in enumerate(row):
if matrix[y][x] == 1:
som = (matrix[y-1][x-1] + matrix[y-1][x] + matrix[y-1][x+1] + matrix[y][x-1] + matrix[y][x+1] + matrix[y+1][x-1] + matrix[y+1][x] + matrix[y+1][x+1])
if som == (2 or 3):
matrix[y][x] = 1
else:
matrix[y][x] = 0
pygame.display.update()
time.sleep(1)
else:
#here somewhere it goes wrong
som = (matrix[y-1][x-1] + matrix[y-1][x] + matrix[y-1][x+1] + matrix[y][x-1] + matrix[y][x+1] + matrix[y+1][x-1] + matrix[y+1][x] + matrix[y+1][x+1])
if som == 3:
matrix[y][x] = 1
else:
matrix[y][x] = 0
When I tried this code without the second else, it worked perfect. Now it gives an error: IndexError: list index out of range.
Also I want that the loop is only repeated when 1 second has passed. When I printed som I could see that it only repeated after a second, but on the display from the game, nothing changed until suddenly ten 1s turned into 0s.
How can I change this, so that after every second the display gets updated?
If you loop over a sequence using enumerate then by definition the valid indices are [0] to [x]. So when you index [x+1] you will index out of bounds.
Similarly, when x == 0 your index [x-1] will be [-1] which will index the back of your sequence, which I doubt is what you're expecting.
Your issue is that if you were in the first row of the matrix, you were trying to access the row above it (y-1) which doesn't exist. The same goes for when you are in the last row and accessing y+1, and the same for the x axis.
When you access index y-1 and y is 0 it won't throw the exception, but it would actually give you the value from the end of the list. The exception is thrown when the index does not exist in the list.
I've made a lot of changes to your code to reduce repetition. It should be a lot easier to understand what is going on and I've also implemented the checks to stop the IndexError and your conditional as mentioned by #ForceBru in the comments.
I have gone with the assumption that if the index doesn't exist, to default the value to 0.
for y,row in enumerate(matrix):
for x,color in enumerate(row):
center = matrix[y][x]
top = matrix[y-1][x] if y > 0 else 0
top_right = matrix[y-1][x+1] if y > 0 and x < len(row)-1 else 0
right = matrix[y][x+1] if x < len(row)-1 else 0
bottom_right = matrix[y+1][x+1] if y < len(matrix)-1 and x < len(row)-1 else 0
bottom = matrix[y+1][x] if y < len(matrix)-1 else 0
bottom_left = matrix[y+1][x-1] if y < len(matrix)-1 and x > 0 else 0
left = matrix[y][x-1] if x > 0 else 0
top_left = matrix[y-1][x-1] if y > 0 and x > 0 else 0
surround_sum = (top_left + top + top_right + left + right + bottom_left + bottom + bottom_right)
if center == 1:
if surround_sum == 2 or surround_sum == 3:
matrix[y][x] = 1
else:
center = 0
pygame.display.update()
time.sleep(1)
else:
#here somewhere it goes wrong
if surround_sum == 3:
matrix[y][x] = 1
else:
matrix[y][x] = 0

Find enclosed spaces in 2D array

So, I'm generating an array of spaces, which have the property that they can be either red or black. However, I want to prevent red from being enclosed by black. I have some examples to show exactly what I mean:
0 0 0 0 0 0 0 1
0 1 0 0 0 0 1 0
1 0 1 0 0 0 0 1
0 1 0 0 1 1 1 0
0 0 0 0 1 0 1 0
1 1 1 0 1 1 1 0
0 0 1 0 0 0 0 0
0 0 1 0 0 0 0 0
If red is 0 and black is 1, then this example contains four enclosures, all of which I want to avoid when I generate the array. The inputs I have are the size of the array and the number of 1s I can generate.
How would I go about doing this?
Does this code fits well for you?
Basically I fill a matrix from left to right, from top to bottom.
When I have to assign 0 or 1 to a cell, I check (north and west) if adding a 1 could enclose a 0; in this case I put a 0, else a random 0 or 1.
import sys, random
n = int(sys.argv[1])
m = int(sys.argv[2])
# fill matrix with zeroes
matrix = [[0 for _ in xrange(m)] for _ in xrange(n)]
# functions to get north, south, west and east
# cell wrt this cell.
# If we are going out of bounds, we suppose the matrix
# is sorrounded by 1s.
def get_n(r, c):
if r <= 0: return 1
return matrix[r - 1][c]
def get_s(r, c):
if r >= n - 1: return 1
return matrix[r + 1][c]
def get_w(r, c):
if c <= 0: return 1
return matrix[r][c - 1]
def get_e(r, c):
if c >= m - 1: return 1
return matrix[r][c + 1]
# Checks if the cell is already enclosed by 3 1s.
def enclosed(r, c):
enclosing = get_n(r, c) + get_s(r, c) + get_w(r, c) + get_e(r, c)
if (enclosing > 3): raise Exception('Got a 0 enclosed by 1s')
return enclosing == 3
for r in xrange(n):
for c in xrange(m):
# check west and north
if enclosed(r, c - 1) or enclosed(r - 1, c):
matrix[r][c] = 0
else:
matrix[r][c] = random.randint(0, 1)
print str(matrix[r][c]) + ' ',
print ''
Sample run: python spaces.py 10 10
So you can do the following:
Fill array with zeroes
Randomly select a point
If the condition holds, flip color
Repeat from step 2 or exit
The condition holds for all-zeros array. It is hold on any iteration. So, by induction, it is also true for the final array.
In the step 4 you can decide whether to stop or continue by doing, say N=a*b*1000 iterations or whether the ratio red/black is close to 1. In both cases, the result would be slightly biased since you start from all zeros.
Now, what is the condition. You have to ensure that all black points connected and all red points connected as well. In other words, there's maximum 2 connected clusters. Flipping a color could create more connected clusters, so you flip only when the its number is one or two. You can do the check quite efficiently using Union-Find algorithm, described here.
Edit: if however you want to permit black points to be surrounded by red ones but not vice-versa, you may change the condition to have any number of black clusters but only 0 or 1 of red clusters.
This would be a possible way to check the condition:
def: findStart(myArr):
for i in range(len(myArr)):
for j in range(len(myArr[0])):
if(myArr[i][j] == 0):
return (i,j)
def: checkCon(myArr, number_Ones):
width = len(myArr[0])
height = len(myArr)
pen = [] #A list of all points that are waiting to get a visit
vis = [] #A list of all points that are already visited
x = findStart(myArr)
while(len(pen) != 0): #Visit points as long as there are points left
p = pen.pop() #Pick a point to visit
if p in vis:
#do nothing since this point already was visited
else:
vis.append(p)
x,y = p
#A vertical check
if(x == 0 and myArr[x+1][y] == 0):
pen.append((x+1,y))
elif(x == (height-1) and myArr[x-1][y] == 0):
pen.append((x-1,y))
else:
if(myArr[x-1][y] == 0 and x-1 >= 0):
pen.append((x-1,y))
if(myArr[x+1][y] == 0):
pen.append((x+1,y))
#A horizontal check
if(y == 0 and myArr[x][y+1] == 0):
pen.append((x,y+1))
elif(y == (width-1) and myArr[x][y-1] == 0):
pen.append((x,y-1))
else:
if(myArr[x][y+1] == 0):
pen.append((x,y+1))
if(myArr[x][y-1] == 0 and y-1 >= 0):
pen.append((x,y-1))
print((height*width-number_Ones) == len(vis)) #if true, alle Zeros are connected and not enclosed
To clarify this is just a concept to check the condition. The idea is to visit all connected zeros and see if there are any left (that are not connected). If that is the case, there are some enclosed.
This method also doesn't work when the 1's form a frame around the matrix like this:
1 1 1 1
1 0 0 1
1 0 0 1
1 1 1 1
Again, just a concept :)
The problem has two parts actually. Generating the board state, and then checking if it is correct. I realised that checking the correctness was actually worse than just being sure correct states were always generated. This is what I did:
Note that I have defined self.WallSpaces to be an array equal in length to the height of my array, comprised of integers with the number of bits equal to the width of my array. self.Width and self.Height provide the end indices for the array. Basically, Intersects works by checking all the spaces surrounding a point for 1s, except the direction the space was "built from" (see below) and returning True if any of these are the edge of the array or a 1.
def Intersects(self, point, direction):
if (point[0] > 0):
if (direction != [1, 0] and self.WallSpaces[point[0] - 1] & (1 << point[1]) != 0):
return True
if (point[1] == 0 or self.WallSpaces[point[0] - 1] & (1 << (point[1] - 1)) != 0):
return True
if (point[1] == self.Width or self.WallSpaces[point[0] - 1] & (1 << (point[1] + 1)) != 0):
return True
else:
return True
if (point[0] < self.Height):
if (direction != [-1, 0] and self.WallSpaces[point[0] + 1] & (1 << point[1]) != 0):
return True
if (point[1] == 0 or self.WallSpaces[point[0] + 1] & (1 << (point[1] - 1)) != 0):
return True
if (point[1] == self.Width or self.WallSpaces[point[0] + 1] & (1 << (point[1] + 1)) != 0):
return True
else:
return True
if (point[1] == 0 or (direction != [0, 1] and self.WallSpaces[ point[0] ] & (1 << (point[1] - 1)) != 0)):
return True
if (point[1] == self.Width or (direction != [0, -1] and self.WallSpaces[ point[0] ] & (1 << (point[1] + 1)) != 0)):
return True
return False
The directions GPacW.Left, GPacW.Right, GPackW.Up, and GPacW.Down represent the cardinal directions for movement. This function works by constructing "walls" in the array from random points, which can turn in random directions, ending when they have intersected twice.
def BuildWalls(self):
numWalls = 0
directions = [ [GPacW.Left, GPacW.Right], [GPacW.Up, GPacW.Down] ]
start = [ random.randint(0, self.Height), random.randint(0, self.Width) ]
length = 0
horizontalOrVertical = random.randint(0, 1)
direction = random.randint(0, 1)
d = directions[horizontalOrVertical][direction]
intersected = False
while (numWalls < self.Walls):
while (start == [0, 0] or start == [self.Height, self.Width] or self.Intersects(start, d)):
start = [ random.randint(0, self.Height), random.randint(0, self.Width) ]
if (length == 0):
horizontalOrVertical = not horizontalOrVertical
direction = random.randint(0, 1)
length = random.randint(3, min(self.Height, self.Width))
d = directions[horizontalOrVertical][direction]
if (self.WallSpaces[ start[0] ] & (1 << start[1] ) == 0):
self.WallSpaces[ start[0] ] |= 1 << start[1]
numWalls += 1
length -= 1
if (0 <= (start[0] + d[0]) <= self.Height and 0 <= (start[1] + d[1]) <= self.Width):
start[0] += d[0]
start[1] += d[1]
else:
start = [0,0]
if (self.Intersects(start, d)):
if (intersected):
intersected = False
start = [0,0]
length = 0
else:
intersected = True
return

Categories

Resources