Efficient method to determine location on a grid(array) - python

I am representing a grid with a 2D list in python. I would like to pick a point (x,y) in the list and determine it's location...right edge, top left corner, somewhere in the middle...
Currently I am checking like so:
# left column, not a corner
if x == 0 and y != 0 and y != self.dim_y - 1:
pass
# right column, not a corner
elif x == self.dim_x - 1 and y != 0 and y != self.dim_y - 1:
pass
# top row, not a corner
elif y == 0 and x != 0 and x != self.dim_x - 1:
pass
# bottom row, not a corner
elif y == self.dim_y - 1 and x != 0 and x != self.dim_x - 1:
pass
# top left corner
elif x == 0 and y == 0:
pass
# top right corner
elif x == self.dim_x - 1 and y == 0:
pass
# bottom left corner
elif x == 0 and y == self.dim_y - 1:
pass
# bottom right corner
elif x == self.dim_x - 1 and y == self.dim_y - 1:
pass
# somewhere in middle; not an edge
else:
pass
Where I have some function do something after the location is determined
dim_x and dim_y are the dimensions of the list.
Is there a better way of doing this without so many if-else statements? Something efficient would be good since this part of the logic is being called a couple million times...it's for simulated annealing.
Thanks in advance. Also, what would be a better way of wording the title?

def location(x,y,dim_x,dim_y):
index = 1*(y==0) + 2*(y==dim_y-1) + 3*(x==0) + 6*(x==dim_x-1)
return ["interior","top","bottom","left","top-left",
"bottom-left","right","top-right","bottom-right"][index]

# initially:
method_list = [
bottom_left, bottom, bottom_right,
left, middle, right,
top_left, top, top_right,
]
# each time:
keyx = 0 if not x else (2 if x == self.dim_x - 1 else 1)
keyy = 0 if not y else (2 if y == self.dim_y - 1 else 1)
key = keyy * 3 + keyx
method_list[key](self, x, y, other_args)
Untested ... but the general idea should shine through.
Update after the goal posts were drastically relocated by "Something efficient would be good since this part of the logic is being called a couple million times...it's for simulated annealing":
Originally you didn't like the chain of tests, and said you were calling a function to handle each of the 8 cases. If you want fast (in Python): retain the chain of tests, and do the handling of each case inline instead of calling a function.
Can you use psyco? Also, consider using Cython.

If I understand correctly, you have a collection of coordinates (x,y) living in a grid, and you would like to know, given any coordinate, whether it is inside the grid or on an edge.
The approach I would take is to normalize the grid before making the comparison, so that its origin is (0,0) and its top right corner is (1,1), then I would only have to know the value of the coordinate to determine its location. Let me explain.
0) Let _max represent the maximum value and _min, for instance, x_min is the minimum value of the coordinate x; let _new represent the normalized value.
1) Given (x,y), compute: x_new = (x_max-x)/(x_max-x_min) and y_new=(y_max-y)/(y_max-y_min).
2) [this is pseudo code]
switch y_new:
case y_new==0: pos_y='bottom'
case y_new==1: pos_y='top'
otherwise: pos_y='%2.2f \% on y', 100*y_new
switch x_new:
case x_new==0: pos_x='left'
case x_new==1: pos_x='right'
otherwise: pos_x='%2.2f \% on x', 100*x_new
print pos_y, pos_x
It would print stuff like "bottom left" or "top right" or "32.58% on y 15.43% on x"
Hope that helps.

I guess if you really want to treat all these cases completely differently, your solution is okay, as it is very explicit. A compact solution might look more elegant, but will probably be harder to maintain. It really depends on what happens inside the if-blocks.
As soon as there is a common handling of, say, the corners, one might prefer to catch those cases with one clever if-statement.

Something like this might be more readable / maintainable. It will probably be a lot faster than your nested if statements since it only tests each condition once and dispatches through a dictionary which is nice and fast.
class LocationThing:
def __init__(self, x, y):
self.dim_x = x
self.dim_y = y
def interior(self):
print "interior"
def left(self):
print "left"
def right(self):
print "right"
def top(self):
print "top"
def bottom(self):
print "bottom"
def top_left(self):
print "top_left"
def top_right(self):
print "top_right"
def bottom_left(self):
print "bottom_left"
def bottom_right(self):
print "bottom_right"
location_map = {
# (left, right, top, bottom)
( False, False, False, False ) : interior,
( True, False, False, False ) : left,
( False, True, False, False ) : right,
( False, False, True, False ) : top,
( False, False, False, True ) : bottom,
( True, False, True, False ) : top_left,
( False, True, True, False ) : top_right,
( True, False, False, True ) : bottom_left,
( False, True, False, True ) : bottom_right,
}
def location(self, x,y):
method = self.location_map[(x==0, x==self.dim_x-1, y==0, y==self.dim_y-1)]
return method(self)
l = LocationThing(10,10)
l.location(0,0)
l.location(0,1)
l.location(1,1)
l.location(9,9)
l.location(9,1)
l.location(1,9)
l.location(0,9)
l.location(9,0)
When you run the above it prints
top_left
left
interior
bottom_right
right
bottom
bottom_left
top_right

For a fast inner-loop function, you can just bite the bullet and do the ugly: nested if else statements with repeated terms, so that each comparison is only done once, and it runs about twice as fast as an example cleaner answer (by mobrule):
import timeit
def f0(x, y, x_dim, y_dim):
if x!=0:
if x!=x_dim: # in the x interior
if y!=0:
if y!=y_dim: # y interior
return "interior"
else: # y==y_dim edge 'top'
return "interior-top"
else:
return "interior-bottom"
else: # x = x_dim, "right"
if y!=0:
if y!=y_dim: #
return "right-interior"
else: # y==y_dim edge 'top'
return "right-top"
else:
return "right-bottom"
else: # x=0 'left'
if y!=0:
if y!=y_dim: # y interior
return "left-interior"
else: # y==y_dim edge 'top'
return "left-top"
else:
return "left-bottom"
r_list = ["interior","top","bottom","left","top-left",
"bottom-left","right","top-right","bottom-right"]
def f1(x,y,dim_x,dim_y):
index = 1*(y==0) + 2*(y==dim_y-1) + 3*(x==0) + 6*(x==dim_x-1)
return r_list[index]
for x, y, x_dim, y_dim in [(4, 4, 5, 6), (0, 0, 5, 6)]:
t = timeit.Timer("f0(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
print "f0", t.timeit(number=1000000)
t = timeit.Timer("f1(x, y, x_dim, y_dim)", "from __main__ import f0, f1, x, y, x_dim, y_dim, r_list")
print "f1", t.timeit(number=1000000)
Which gives:
f0 0.729887008667 # nested if-else for interior point (no "else"s)
f1 1.4765329361
f0 0.622623920441 # nested if-else for left-bottom (all "else"s)
f1 1.49259114265
So it's a bit better than twice as fast as mobrule's answer, which was the fastest looking code that I knew would work when I posted this. (Also, I moved mobrule's string list out of the function as that sped up the result by 50%.) Speed over beauty?
If instead you want a concise and easy to read solution, I suggest:
def f1(x, y, x_dim, y_dim):
d_x = {0:"left", x_dim:"right"}
d_y = {0:"bottom", y_dim:"top"}
return d_x.get(x, "interior")+"-"+d_y.get(y, "interior")
which is as fast as the others by my timing.

Related

LeetCode: Flood Fill, Recursion in For Loop stuck in endless loop

I was working on this specific LeetCode problem and I encountered a problem where I would be stuck recursing. The way I understand it, if an input type is mutable, the input should be pass by reference, so they should be referencing the same thing. Can someone explain how my method breaks? I really want to try solving this problem using recursion, but I don't understand how to do it using my method. My code first finds north, east,south,west, and then determines if they are valid. It then determines if among those directions if they have the same count as the original node.
Of those that have the same count as the original node, I need to recurse on those and repeat the process until all nodes have the value of newColor
https://leetcode.com/problems/flood-fill/
class Solution:
def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]:
top = (sr-1, sc)
down = (sr+1, sc)
left = (sr, sc-1)
right = (sr, sc+1)
# Possible Directions
posDirec = [direc for direc in [top,down,left,right] if direc[0] >=0 and direc[1] >=0 and direc[0] < len(image) and direc[1] < len(image[0])]
# Neighbors that we can traverse
posNeigh = [e for e in posDirec if image[e[0]][e[1]] == image[sr][sc]]
image[sr][sc] = newColor
# print(image, '\n')
print(len(posNeigh), posNeigh, image)
if len(posNeigh) == 0:
pass
else:
for neigh in posNeigh: #top, down,left, right of only valids
self.floodFill(image, neigh[0], neigh[1], newColor)
return image
At the very end, my program should return the image. I want to return the image at the end, however, my code ends up stuck in recursion
Take a look at the following line:
# Neighbors that we can traverse
posNeigh = [e for e in posDirec if image[e[0]][e[1]] == image[sr][sc]]
This condition fails to account for the possibility that image[e[0]][e[1]] has already been filled in with newColor, resulting in an infinite loop between filled cells and a stack overflow.
If we change it to
posNeigh = [
e for e in posDirec
if image[e[0]][e[1]] == image[sr][sc]
and image[e[0]][e[1]] != newColor # <-- added
]
we can make sure we're not revisiting previously-filled areas.
Given that the list comprehensions have grown quite unwieldy, you might consider a rewrite:
def floodFill(self, image, sr, sc, new_color):
target_color = image[sr][sc]
image[sr][sc] = new_color
for y, x in ((sr + 1, sc), (sr, sc - 1), (sr, sc + 1), (sr - 1, sc)):
if y >= 0 and x >= 0 and y < len(image) and x < len(image[0]) and \
image[y][x] != new_color and image[y][x] == target_color:
self.floodFill(image, y, x, new_color)
return image
A mutable input does not pass by reference. The way I see it, solving it using recursion is not possible. Try an iterative solution.

How to fix my algorithm for building visibility graph?

I'm trying to write a code that generates a visibility graph from a set of points and walls (obstacles). My algorithms is not correct and fails on some cases where there is more than one wall intersecting an edge between two points.
Here's kind of a pseudo-python code for my algorithm :
Intersect(wall, P, Q):
returns True if wall segment intersects with PQ segment
Cross(wall, P, Q):
returns True if wall segment crosses PQ segment
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
flag = True
for wall in walls:
if (Cross(wall, nodes[i].pos, nodes[j].pos)):
flag = False
if (flag):
nodes[i].adj.append(nodes[j])
nodes[j].adj.append(nodes[i])
How can I fix my algorithm?
Here's one of the tests where it fails:
Walls :
w1 -> (1, 0),(2, 1)
w2 -> (2, 1),(3, 2)
Nodes to be checked:
node1 -> (0, 2)
node2 -> (4, 0)
There shouldn't be an edge but my algorithm generates an edge because the edge does not Cross any wall (it intersects but not cross).
For clarification, Cross means that two segments intersect (share a point,) but they don't share any point that is either the start or end of any of the two segments.
When the view ray just grazes a wall like this, you need to keep track of whether the grazing was at the left edge of the wall or at the right edge, as seen from viewpoint P.
def LeftIntersect(wall, P, Q):
if Cross(wall, P, Q):
return False
if not Intersect(wall, P, Q):
return False
if magnitude(cross_product(PQ, wall_midpoint)) <= 0:
return False
return True
def RightIntersect(wall, P, Q):
if Cross(wall, P, Q):
return False
if not Intersect(wall, P, Q):
return False
if magnitude(cross_product(PQ, wall_midpoint)) >= 0:
return False
return True
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
crossCount = 0
leftIntersectCount = 0
rightIntersectCount = 0
for wall in walls:
if (Cross(wall, nodes[i].pos, nodes[j].pos)):
crossCount += 1
if (LeftIntersect(wall, nodes[i].pos, nodes[j].pos)):
leftIntersectCount += 1
if (RightIntersect(wall, nodes[i].pos, nodes[j].pos)):
rightIntersectCount += 1
visible = True
if (crossCount > 0)
visible = False
if (leftIntersect > 0 && rightIntersect > 0)
visible = False
if (visible):
nodes[i].adj.append(nodes[j])
nodes[j].adj.append(nodes[i])
The first way that comes to mind for me is to check every pair of three from [node_a, node_b, wall_start, wall_end] and see if the third point lines along the segment between the other two. A quick and accurate way to do this is first create a vector between each of the points, and take two dot products to make sure the "middle" point really does lie in the middle. Additionally is necessary to also check the direction of the vectors to make sure they are parallel, which is equally fast. If both pass, then the third point lies along the segment between the other two.
In python
def intersect(a, b, c):
(ax, ay), (bx, by), (cx, cy) = a, b, c
bax, bay = bx-ax, by-ay
bcx, bcy = bx-cx, by-cy
acx, acy = ax-cx, ay-cy
if bax*bcx + bay*bcy < 0: return False
if bax*acx + bay*acy > 0: return False
return bax*bcy == bay*bcx
In practice, it might be better to check bax*bcy == bay*bcx first, since it is just as fast but probably more likely to fail (and break early) for non-intersecting points.
To then check if any two points intersects a given wall-
def wall_hit(node1, node2, wall_start, wall_end):
return intersect(node1, node2, wall_start) or \
intersect(node1, node2, wall_end) or \
intersect(wall_start, wall_end, node1) or \
intersect(wall_start, wall_end, node2)
Since most of the checks will effectively "short-circuit" after the first or second check in each intersect() call, and each wall_hit() will short-circuit if any of them do hit, I don't think this would be too costly to implement.
If you need to optimize it, you could always compute + reuse the bax, bay = bx-ax, by-ay; ... calculations by either inlining all the function calls and reordering, or by computing them in a separate function and then caching with the lru_cache decorator from functools. Additionally, if you go with the inlining approach, you can likely reorder the conditionals and bax, bay = ... calculations to lazy evaluate them so that you may not need to compute all the intermediate values to assert hit/no_hit.

Python while loop terminating early

I am trying to make a Python program that churns out a solved sudoku puzzle. It randomly generates coordinates for a tile, and if that tile already has a number in it, it tries again. It then generates a number between 1 and 9 to put there, and if that number isn't already in that row, column, or section, it'll assign the value and add those coordinates to the list of occupied tiles. Once all the tiles are filled, it's supposed to exit the loop and return the completed grid.
The trouble is, it's always stopping for no reason after about 70 loops, causing the program to freeze.
Here is the code for the function I'm talking about:
def populate(grid):
usedCoords = []
populated = False
while not populated:
x = random.randrange(len(grid))
y = random.randrange(len(grid))
while [x,y] in usedCoords:
x = random.randrange(len(grid))
y = random.randrange(len(grid))
value = random.randrange(1, len(grid) + 1)
if not rowCheck(grid, x, y, value) and not columnCheck(grid, x, y, value) and not squareCheck(grid, x, y, value):
grid[x][y] = value
usedCoords.append([x,y])
print(len(usedCoords))
if len(usedCoords) == len(grid) ** 2:
populated = True
return grid
And here is the code for the functions it references:
def rowCheck(grid, x, y, value):
for i in range(len(grid)):
if not i == x:
if grid[i][y] == value:
return True
return False
def columnCheck(grid, x, y, value):
for i in range(len(grid)):
if not i==y:
if grid[x][i] == value:
return True
return False
def squareCheck(grid, x, y, value):
grid2 = [0] * (sectionSide) #new grid for the specific section
for i in range(len(grid2)):
grid2[i] = [0] * sectionSide
for i in range(x - (sectionSide - 1), x + sectionSide): #scanning only nearby coordinates
for j in range(y - (sectionSide - 1), y + sectionSide):
try:
if i // sectionSide == x // sectionSide and j // sectionSide == y // sectionSide:
grid2[i][j] = grid[x][y]
except IndexError:
pass
for i in range(len(grid2)):
for j in range(len(grid2[i])):
if grid2[i][j] == value and not (i == x and j == y):
return True
return False
There may be other issues, but a big problem with your code is that it has no way to backtrack if it finds it's created a board state that cannot be solved. Consider what would happen if your code put the following values on the first two rows of the board:
1 2 3 4 5 6 7 8 9
4 5 6 7 8 1 2 3
The numbers that have been placed so far are all legal, but there is no number that you can put in the last space of the second row. I'd guess that your code is eventually getting stuck when it creates a bunch of board positions like this, which cannot take any value. If there are no legal moves left it can make, it will keep on looping forever.
You need a more sophisticated algorithm to avoid this issue.

Connect 4 Game logic

am developing http://en.wikipedia.org/wiki/Connect_Four game in python using tkinter.
I have come up with the board and the two player pieces. I am now trying to check if the game is over. I have implemented the following logic, but this doesnt seem to work.
def checkWin():
for row in range(canvas.data.rows):
for col in range(canvas.data.cols):
checkWinFromCell(row, col)
def checkWinFromCell(row, col):
if canvas.data.board[row][col] == 0:
return False
dirs = [[0,1], [1,0], [1,1], [1,-1], [0,-1], [-1,0], [-1,-1], [-1,1]]
for direction in dirs:
checkWinFromCellInDir(row, col, direction)
return False
def checkWinFromCellInDir(row, col, direction):
drow, dcol = direction[0], direction[1]
for i in range(1,4):
if row+i*drow<0 or row+i*drow>=canvas.data.rows or col+i*dcol<0 or col+i*dcol>=canvas.data.cols:
return False
if canvas.data.board[row][col] != canvas.data.board[row+i*drow][col+i*dcol]:
return False
return canvas.data.board[row][col]
I need to know the logic to check if my game has been completed ie the four dots have been connected.
I'm not very familiar with Tkinter, so this is a halfhearted answer at best. However since it's been nearly an hour and no answer is forthcoming, I did work one up for you.
class Map(list):
def __init__(self, tiles, width, height):
"""This object functions exactly as a tile map for your connect four
game. It is a subclass of list, so you can iterate through its rows.
"y" increases from top to bottom and "x" increases from left to right"""
for y in range(height):
self.append([random.choice(tiles) for x in range(width)])
# for ease of use, we're generating a random tile map here
def __str__(self):
return '\n'.join([' '.join([ch for ch in row]) for row in self])
# this will make print(map_object) show something pretty
Vector = collections.namedtuple("Vector", ['x','y'])
# build a namedtuple to contain our directions. It's a bit easier on the eyes
# to use object access, IMO. YMMV.
def checkwin(map_):
directions = [Vector(x, y) for (x, y) in [(1, 0), (-1, 1), (0, 1), (1, 1)]]
# directions = E, SW, S, SE
for y, row in enumerate(map_):
for x, ch in enumerate(row):
value = ch
if value == ' ': continue # blank squares can't win
for vector in directions:
result = checkfour(map_, x, y, vector)
if result:
return result
return False
def checkfour(map_, x, y, vector):
"""Checks map_ for four squares from the given x and y in vector direction"""
value = map_[y][x]
try:
lst = [map_[y + k*vector.y][x + k*vector.x]==value for k in range(1,4)]
# 2 2 2 1 would return [True, True, False]
return all(lst) and (x,y)
except IndexError:
return False
# we know we'll run off the edge of the map. It's cheaper (in programmer
# time) to simply return False here rather than check each square to make
# sure there ARE 3 more squares in vector-direction.
map_ = Map("12 ", 8, 8)
print(checkwin(map_))
# if your randomly generated map would win in connect four, it should print
# the first (x,y) coordinate that begins a win going E, SW, S, or SE
print(map_)

Python: List index out of range, don't know why

I'm trying to iterate through a matrix and check for the number of cells touching the current cell that have a value of 1. I'm getting an out of bounds exception and I'm not sure why.
for x in range(0,ROWS):
for y in range(0,COLS):
#get neighbors
neighbors = []
if x!=0 & y!=COLS:
neighbors.append([x-1,y+1])
if y!=COLS:
neighbors.append([x,y+1])
if x!=ROWS & y!=COLS:
neighbors.append([x+1,y+1])
if x!=0:
neighbors.append([x-1,y])
if x!=ROWS:
neighbors.append([x+1,y])
if x!=0 & y!=0:
neighbors.append([x-1,y-1])
if y!=0:
neighbors.append([x,y-1])
if x!=ROWS & y!=0:
neighbors.append([x+1,y-1])
#determine # of living neighbors
alive = []
for i in neighbors:
if matrix[i[0]][i[1]] == 1:
alive.append(i)
I'm getting the error
IndexError: list index out of range
at this line if matrix[i[0]][i[1]] == 1:
Why is this out of range and how should I fix it?
The problem is that you are using &. This is a bit-wise AND, not a logical AND. In Python, you just use and. For example:
if x!=0 and y!=COLS:
neighbors.append([x-1,y+1])
However the real reason why using the bit-wise AND causes a problem is order of operations - it has a higher precedence!
>>> 1 != 2 & 3 != 3
True
>>> (1 != 2) & (3 != 3)
False
>>> 1 != (2 & 3) != 3
True
So even though your logic looks right, order of operations means that your code's actual behavior is drastically different than what you were expecting.
The other issue with your code is that you are checking if x and y are equal to ROWS and COLS, rather than if they are equal to ROWS-1 and COLS-1, which are the true boundary conditions.
EDIT: I THINK I FOUND IT
Upon further inspection of your code, I found that you're using if x!=0 & y!=0. This is bit-wise AND not logical AND, so it's not going to give you the result you want. Use and instead of & and see if your problem goes away.
I would refactor this slightly to make it easier to read.
for loc_x in range(ROWS):
for loc_y in range(COLS): # btw shouldn't ROWS/COLS be flipped?
# if your matrix isn't square this could be why
x_values = [loc_x]
if loc_x < ROWS: x_values.append(loc_x+1)
if loc_x > 0: x_values.append(loc_x-1)
y_values = [loc_y]
if loc_y < COLS: y_values.append(loc_y+1)
if loc_y > 0: y_values.append(loc_y-1)
neighbors = [(x,y) for x in x_values for y in y_values if (x,y) != (loc_x,loc_y)]
alive = [matrix[n[0]][n[1]] for n in neighbors if matrix[n[0]][n[1]]==1]
Try running this with your code and see if it doesn't solve the issue. If not, you may need to test further. For instance, wrap the alive definition in try/except tags that will give a better traceback.
try:
alive = ...
except IndexError:
print("Location: {},{}\nneighbors: {}\nROWS:{}\nCOLS:{}".format(x_loc,y_loc, neighbors,ROWS,COLS))
raise
As an aside, I've solved this problem before by creating objects that held the linked information and going top-to-bottom left-to-right and having each check the field to its right and below it. E.g.:
class Field(object):
def __init__(self,x,y,value):
self.x = x
self.y = y
self.value = value
self.neighbors = neighbors
class Matrix(list):
def __init__(self,size):
self.ROWS,self.COLS = map(int,size.lower().split("x"))
for y in range(ROWS):
self.append([Field(x,y,random.randint(0,1)) for x in range(COLS)])
self.plot()
def plot(self):
for row in self:
for col in row:
try:
self[row][col].neighbors.append(self[row+1][col])
self[row+1][col].neighbors.append(self[row][col])
except IndexError: pass
try:
self[row][col].neighbors.append(self[row][col+1])
self[row][col+1].neighbors.append(self[row][col])
except IndexError: pass
Of course this doesn't take care of diagonals. I'm pretty sure you can figure out how to manage those, though!!

Categories

Resources