Constructing and printing a maze - python

I have implemented a maze with python
a function addCoordinate where x and y denote the x and y coord of grid and block type: zero means open and 1 means wall.
a printMaze function which prints the maze with * for wall and empty space for open spaces.
My
Implementation seems to be working fine but could there be a better way of doing this:
class Maze:
board, max_x, max_y, list = [],2,2,[]
def __init__(self):
"""
Constructor - You may modify this, but please do not add any extra parameters
"""
def addCoordinate(self, x, y, blockType):
"""
Add information about a coordinate on the maze grid
x is the x coordinate
y is the y coordinate
blockType should be 0 (for an open space) of 1 (for a wall)
"""
if x > self.max_x:
self.max_x = x
if y > self.max_y:
self.max_y = y
if self.max_y >= len(self.board) or self.max_x >= len(self.board[0]):
newboard = [[1 for a in range(self.max_x + 1)] for b in range(self.max_y + 1)]
for i in range(len(self.board)):
for j in range(len(self.board[i])):
newboard[i][j] = self.board[i][j]
self.board = newboard
self.board[y][x] = blockType
def printMaze(self):
"""
Print out an ascii representation of the maze.
A * indicates a wall and a empty space indicates an open space in the maze
"""
for i in range(self.max_x + 1):
for j in range(self.max_y + 1):
if self.board[i][j] == 0:
print " ",
else:
print "*",
print ""
def mazeTest():
"""
This sets the open space coordinates for the example
maze in the assignment.
The remainder of coordinates within the max bounds of these specified coordinates
are assumed to be walls
"""
myMaze = Maze()
myMaze.addCoordinate(1, 0, 0)
myMaze.addCoordinate(1, 1, 0)
myMaze.addCoordinate(7, 1, 0)
myMaze.addCoordinate(1, 2, 0)
myMaze.addCoordinate(2, 2, 0)
myMaze.addCoordinate(3, 2, 0)
myMaze.addCoordinate(4, 2, 0)
myMaze.addCoordinate(6, 2, 0)
myMaze.addCoordinate(7, 2, 0)
myMaze.addCoordinate(4, 3, 0)
myMaze.addCoordinate(7, 3, 0)
myMaze.addCoordinate(4, 4, 0)
myMaze.addCoordinate(7, 4, 0)
myMaze.addCoordinate(3, 5, 0)
myMaze.addCoordinate(4, 5, 0)
myMaze.addCoordinate(7, 5, 0)
myMaze.addCoordinate(1, 6, 0)
myMaze.addCoordinate(2, 6, 0)
myMaze.addCoordinate(3, 6, 0)
myMaze.addCoordinate(4, 6, 0)
myMaze.addCoordinate(6, 6, 0)
myMaze.addCoordinate(7, 6, 0)
myMaze.addCoordinate(5, 7, 0)
myMaze.printMaze()
mazeTest()
Is there any data structures i should be using other than a list. i am looking to implement a maze solving algorithm to this maze

Related

Backtracking pathinding problem in Python

Recently, I've found out about backtracking and without much thinking started on the book from the guy who has shown some Sudoku backtracking tricks (https://www.youtube.com/watch?v=G_UYXzGuqvM&ab_channel=Computerphile. Unfortunately, I'm stuck with the first backtracking problem without the solution.
The problem is formulated accordingly:
Use backtracking to calculate the number of all paths from the bottom left to the top right corner in a
x * y-grid. This includes paths like https://imgur.com/3t3Np4M. Note that every point can only be visited once. Write a function np(x,y) that returns the number of paths in a x*y-grid. E.g. np(2,3) should return 38. Hint: Create a grid of booleans where you mark the positions already visited.
Whatever I change in this short block of code I'm landing nowhere near 38.
```
grid = [[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0]]
solution = 0
def number_of_paths(x, y):
global solution
global grid
for i in range(0, x):
for j in range(0, y):
if grid[i][j] == 0:
grid[i][j] = 1
number_of_paths(x, y)
grid[i][j] = 0
solution += 1
return
if __name__ == '__main__':
number_of_paths(2, 3)
print(grid)
print(solution)```
That's a sample solution with solution with Sudoku solver.
```
grid = [[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9]]
import numpy as np
def possible(y, x, n):
global grid
for i in range(0, 9):
if grid[y][i] == n:
return False
for i in range(0, 9):
if grid[i][x] == n:
return False
x0 = (x // 3) * 3
y0 = (y // 3) * 3
for i in range(0, 3):
for j in range(0, 3):
if grid[y0 + i][x0 + j] == n:
return False
return True
def solve():
global grid
for y in range(9):
for x in range(9):
if grid[y][x] == 0:
for n in range(1, 10):
if possible(y, x, n):
grid[y][x] = n
solve()
# backtracking - bad choice
grid[y][x] = 0
return
print(np,matrix(grid))
input("More?")```
A few suggestions:
You might want to use a set for a grid, adding a square as soon as it is visited, if it is not a member of the set yet.
The counter and the grid can be global but it would probably be easier for you to take them as arguments for the function at first. After the solution is clearer you can worry about those details.
You are going about the problem the wrong way. It would be good to have one function calculating the number of paths from the origin to the destination (by calling the function for the neighbors that have not been visited yet. Make sure you update the grid). On top of that you can have a function that calls the path function for every combination of origin and destination. A small tip: You do not have to calculate the same path in reverse direction! You can have a map of calculate sums of paths. If the opposite direction has been calculate, don't bother. Later, double the amount of paths by 2.
Good luck!
I will show you a solution on a coordinate system where (0,0) is the topleft and (maxY,maxX) is the bot right. Going right increases x and going down increases y.
1- If you are trying to solve the exact maze in the image, then your grid array shape is wrong. Notice that you are travelling between corners of the squares, there are 4 points you can be horizontally and 3 points you can be vertically.
2- Hint is telling you about using a boolean mask for visited state, you already have a grid array so a separate array is not necessary.
3- The main problem with your code is how you are progressing in the maze. The loop structure
for i in range(0, x):
for j in range(0, y):
does not make sense because when you are in a position (x, y), you can only move in 4 main directions (right, up, left, down). However this loops make it look like you are trying to branch into all positions behind you, which is not valid. In my code I will explicity show about this traverse stuff.
grid = [[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 0, 0, 0]]
# number of solutions
solution = 0
# maximum values of x and y coordinates
maxX = len(grid[0])-1
maxY = len(grid)-1
# endpoint coordinates, top(y=0) right(x=maxX) of the maze
endX = maxX
endY = 0
# starting point coordinates, bottom(y=maxY) left(x=0) of the maze
mazeStartX = 0
mazeStartY = maxY
def number_of_paths(startX, startY):
global solution
global grid
global mask
# if we reached the goal, return at this point
if (startX == endX and startY == endY):
solution += 1
return
# possible directions are
#RIGHT (+1x, 0y)
#UP (0x, -1y)
#LEFT (-1x, 0y)
#DOWN (0x, +1y)
# I use a direction array like this to avoid nested ifs inside the for loop
dx = [1, 0, -1, 0]
dy = [0, -1, 0, 1]
for d in range(len(dx)):
newX = startX + dx[d]
newY = startY + dy[d]
# out of maze bounds
if (newX < 0 or newY < 0):
continue
# out of maze bounds
if (newX > maxX or newY > maxY):
continue
if (grid[newY][newX] == 1):
# this are is already visited
continue
else:
# branch from this point
grid[newY][newX] = 1
number_of_paths(newX, newY)
grid[newY][newX] = 0
if __name__ == '__main__':
number_of_paths(mazeStartX, mazeStartY)
print(grid)
print(solution)

Finding similar sub-sequences in a time series?

I have thousands of time series (24 dimensional data -- 1 dimension for each hour of the day). Out of these time series, I'm interested in a particular sub-sequence or pattern that looks like this:
I'm interested in sub-sequences that resemble the overall shape of the highlighted section -- that is, a sub-sequence with a sharp negative slope, followed by a period of several hours where the slope is relatively flat before finally ending with a sharp positive slope. I know the sub-sequences I'm interested in won't match each other exactly and most likely will be shifted in time, scaled differently, have longer/shorter periods where the slope is relatively flat, etc. but I would like to find a way to detect them all.
To do this, I have developed a simple Heuristic (based on my definition of the highlighted section) to quickly find some of the sub-sequences of interest. However, I was wondering if there was a more elegant way (in Python) to search thousands of time series for the sub-sequence I'm interested in (while taking into account things mentioned above -- differences in time, scale, etc.)?
Edit: a year later I cannot believe how much I overcomplicated flatline and slope detection; stumbling on the same question, I realized it's as simple as
idxs = np.where(x[1:] - x[:-1] == 0)
idxs = [i for idx in idxs for i in (idx, idx + 1)]
First line is implemented efficiently via np.diff(x); further, to e.g. detect slope > 5, use np.diff(x) > 5. The second line is since differencing tosses out right endpoints (e.g. diff([5,6,6,6,7]) = [1,0,0,1] -> idxs=[1,2], excludes 3,.
Functions below should do; code written with intuitive variable & method names, and should be self-explanatory with some readovers. The code is efficient and scalable.
Functionalities:
Specify min & max flatline length
Specify min & max slopes for left & right tails
Specify min & max average slopes for left & right tails, over multiple intervals
Example:
import numpy as np
import matplotlib.pyplot as plt
# Toy data
t = np.array([[ 5, 3, 3, 5, 3, 3, 3, 3, 3, 5, 5, 3, 3, 0, 4,
1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 0, 3, 3,
5, 5, 3, 3, 3, 3, 3, 5, 7, 3, 3, 5]]).T
plt.plot(t)
plt.show()
# Get flatline indices
indices = get_flatline_indices(t, min_len=4, max_len=5)
plt.plot(t)
for idx in indices:
plt.plot(idx, t[idx], marker='o', color='r')
plt.show()
# Filter by edge slopes
lims_left = (-10, -2)
lims_right = (2, 10)
averaging_intervals = [1, 2, 3]
indices_filtered = filter_by_tail_slopes(indices, t, lims_left, lims_right,
averaging_intervals)
plt.plot(t)
for idx in indices_filtered:
plt.plot(idx, t[idx], marker='o', color='r')
plt.show()
def get_flatline_indices(sequence, min_len=2, max_len=6):
indices=[]
elem_idx = 0
max_elem_idx = len(sequence) - min_len
while elem_idx < max_elem_idx:
current_elem = sequence[elem_idx]
next_elem = sequence[elem_idx+1]
flatline_len = 0
if current_elem == next_elem:
while current_elem == next_elem:
flatline_len += 1
next_elem = sequence[elem_idx + flatline_len]
if flatline_len >= min_len:
if flatline_len > max_len:
flatline_len = max_len
trim_start = elem_idx
trim_end = trim_start + flatline_len
indices_to_append = [index for index in range(trim_start, trim_end)]
indices += indices_to_append
elem_idx += flatline_len
flatline_len = 0
else:
elem_idx += 1
return indices if not all([(entry == []) for entry in indices]) else []
def filter_by_tail_slopes(indices, data, lims_left, lims_right, averaging_intervals=1):
indices_filtered = []
indices_temp, tails_temp = [], []
got_left, got_right = False, False
for idx in indices:
slopes_left, slopes_right = _get_slopes(data, idx, averaging_intervals)
for tail_left, slope_left in enumerate(slopes_left):
if _valid_slope(slope_left, lims_left):
if got_left:
indices_temp = [] # discard prev if twice in a row
tails_temp = []
indices_temp.append(idx)
tails_temp.append(tail_left + 1)
got_left = True
if got_left:
for edge_right, slope_right in enumerate(slopes_right):
if _valid_slope(slope_right, lims_right):
if got_right:
indices_temp.pop(-1)
tails_temp.pop(-1)
indices_temp.append(idx)
tails_temp.append(edge_right + 1)
got_right = True
if got_left and got_right:
left_append = indices_temp[0] - tails_temp[0]
right_append = indices_temp[1] + tails_temp[1]
indices_filtered.append(_fill_range(left_append, right_append))
indices_temp = []
tails_temp = []
got_left, got_right = False, False
return indices_filtered
def _get_slopes(data, idx, averaging_intervals):
if type(averaging_intervals) == int:
averaging_intervals = [averaging_intervals]
slopes_left, slopes_right = [], []
for interval in averaging_intervals:
slopes_left += [(data[idx] - data[idx-interval]) / interval]
slopes_right += [(data[idx+interval] - data[idx]) / interval]
return slopes_left, slopes_right
def _valid_slope(slope, lims):
min_slope, max_slope = lims
return (slope >= min_slope) and (slope <= max_slope)
def _fill_range(_min, _max):
return [i for i in range(_min, _max + 1)]

Sorting according to clockwise point coordinates

Given a list in Python containing 8 x, y coordinate values (all positive) of 4 points as [x1, x2, x3, x4, y1, y2, y3, y4] ((xi, yi) are x and y coordinates of ith point ),
How can I sort it such that new list [a1, a2, a3, a4, b1, b2, b3, b4] is such that coordinates (ai, bi) of 1 2 3 4 are clockwise in order with 1 closest to origin of xy plane, i.e. something like
2--------3
| |
| |
| |
1--------4
Points will roughly form a parallelogram.
Currently, I am thinking of finding point with least value of (x+y) as 1, then 2 by the point with least x in remaining coordinates, 3 by largest value of (x + y) and 4 as the remaining point
You should use a list of 2-item tuples as your data structure to represent a variable number of coordinates in a meaningful way.
from functools import reduce
import operator
import math
coords = [(0, 1), (1, 0), (1, 1), (0, 0)]
center = tuple(map(operator.truediv, reduce(lambda x, y: map(operator.add, x, y), coords), [len(coords)] * 2))
print(sorted(coords, key=lambda coord: (-135 - math.degrees(math.atan2(*tuple(map(operator.sub, coord, center))[::-1]))) % 360))
This outputs:
[(0, 0), (0, 1), (1, 1), (1, 0)]
import math
def centeroidpython(data):
x, y = zip(*data)
l = len(x)
return sum(x) / l, sum(y) / l
xy = [405952.0, 408139.0, 407978.0, 405978.0, 6754659.0, 6752257.0, 6754740.0, 6752378.0]
xy_pairs = list(zip(xy[:int(len(xy)/2)], xy[int(len(xy)/2):]))
centroid_x, centroid_y = centeroidpython(xy_pairs)
xy_sorted = sorted(xy_pairs, key = lambda x: math.atan2((x[1]-centroid_y),(x[0]-centroid_x)))
xy_sorted_x_first_then_y = [coord for pair in list(zip(*xy_sorted)) for coord in pair]
# P4=8,10 P1=3,5 P2=8,5 P3=3,10
points=[8,3,8,3,10,5,5,10]
k=0
#we know these numbers are extreme and data won't be bigger than these
xmin=1000
xmax=-1000
ymin=1000
ymax=-1000
#finding min and max values of x and y
for i in points:
if k<4:
if (xmin>i): xmin=i
if (xmax<i): xmax=i
else:
if (ymin>i): ymin=i
if (ymax<i): ymax=i
k +=1
sortedlist=[xmin,xmin,xmax,xmax,ymin,ymax,ymax,ymin]
print(sortedlist)
output:[3, 3, 8, 8, 5, 10, 10, 5]
for other regions you need to change sortedlist line. if center is inside the box then it will require more condition controlling
What we want to sort by is the angle from the start coordinate. I've used numpy here to interpret each vector from the starting coordinate as a complex number, for which there is an easy way of computing the angle (counterclockwise along the unit sphere)
def angle_with_start(coord, start):
vec = coord - start
return np.angle(np.complex(vec[0], vec[1]))
Full code:
import itertools
import numpy as np
def angle_with_start(coord, start):
vec = coord - start
return np.angle(np.complex(vec[0], vec[1]))
def sort_clockwise(points):
# convert into a coordinate system
# (1, 1, 1, 2) -> (1, 1), (1, 2)
coords = [np.array([points[i], points[i+4]]) for i in range(len(points) // 2)]
# find the point closest to the origin,
# this becomes our starting point
coords = sorted(coords, key=lambda coord: np.linalg.norm(coord))
start = coords[0]
rest = coords[1:]
# sort the remaining coordinates by angle
# with reverse=True because we want to sort by clockwise angle
rest = sorted(rest, key=lambda coord: angle_with_start(coord, start), reverse=True)
# our first coordinate should be our starting point
rest.insert(0, start)
# convert into the proper coordinate format
# (1, 1), (1, 2) -> (1, 1, 1, 2)
return list(itertools.chain.from_iterable(zip(*rest)))
Behavior on some sample inputs:
In [1]: a
Out[1]: [1, 1, 2, 2, 1, 2, 1, 2]
In [2]: sort_clockwise(a)
Out[2]: [1, 1, 2, 2, 1, 2, 2, 1]
In [3]: b
Out[3]: [1, 2, 0, 2, 1, 2, 3, 1]
In [4]: sort_clockwise(b)
Out[4]: [1, 0, 2, 2, 1, 3, 2, 1]
Based on BERA's answer but as a class:
code
import math
def class Sorter:
#staticmethod
def centerXY(xylist):
x, y = zip(*xylist)
l = len(x)
return sum(x) / l, sum(y) / l
#staticmethod
def sortPoints(xylist):
cx, cy = Sorter.centerXY(xylist)
xy_sorted = sorted(xylist, key = lambda x: math.atan2((x[1]-cy),(x[0]-cx)))
return xy_sorted
test
def test_SortPoints():
points=[(0,0),(0,1),(1,1),(1,0)]
center=Sorter.centerXY(points)
assert center==(0.5,0.5)
sortedPoints=Sorter.sortPoints(points)
assert sortedPoints==[(0, 0), (1, 0), (1, 1), (0, 1)]
As suggested by IgnacioVazquez-Abrams, we can also do sorting according to atan2 angles:
Code:
import math
import copy
import matplotlib.pyplot as plt
a = [2, 4, 5, 1, 0.5, 4, 0, 4]
print(a)
def clock(a):
angles = []
(x0, y0) = ((a[0]+a[1]+a[2]+a[3])/4, (a[4]+ a[5] + a[6] + a[7])/4) # centroid
for j in range(4):
(dx, dy) = (a[j] - x0, a[j+4] - y0)
angles.append(math.degrees(math.atan2(float(dy), float(dx))))
for k in range(4):
angles.append(angles[k] + 800)
# print(angles)
z = [copy.copy(x) for (y,x) in sorted(zip(angles,a), key=lambda pair: pair[0])]
print("z is: ", z)
plt.scatter(a[:4], a[4:8])
plt.show()
clock(a)
Output is :
[2, 4, 5, 1, 0.5, 4, 0, 4]
[-121.60750224624891, 61.92751306414704, -46.73570458892839, 136.8476102659946, 678.3924977537511, 861.9275130641471, 753.2642954110717, 936.8476102659946]
z is: [2, 5, 4, 1, 0.5, 0, 4, 4]
Try this line of code
def sort_clockwise(pts):
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect

finding shorted path to a position using python

Guys this is a question i got part of google foobar challege
You have maps of parts of the space station, each starting at a prison
exit and ending at the door to an escape pod. The map is represented
as a matrix of 0s and 1s, where 0s are passable space and 1s are
impassable walls. The door out of the prison is at the top left
(0,0)(0,0) and the door into an escape pod is at the bottom right
(w−1,h−1)(w−1,h−1).
Write a function answer(map) that generates the length of the shortest
path from the prison door to the escape pod, where you are allowed to
remove one wall as part of your remodeling plans. The path length is
the total number of nodes you pass through, counting both the entrance
and exit nodes. The starting and ending positions are always passable
(0). The map will always be solvable, though you may or may not need
to remove a wall. The height and width of the map can be from 2 to 20.
Moves can only be made in cardinal directions; no diagonal moves are
allowed.
Test cases
Input:
maze = [[0, 1, 1, 0], [0, 0, 0, 1], [1, 1, 0, 0], [1, 1, 1, 0]]
Output:
7 Input:
maze = [[0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]] Output:
11
and this is its answer i got online
from collections import deque
class Node:
def __init__(self, x, y, saldo, grid):
self.x = x
self.y = y;
self.saldo = saldo
self.grid = grid
def __hash__(self):
return self.x ^ self.y
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.saldo == other.saldo
def get_neighbors(self):
neighbors = []
x = self.x
y = self.y
saldo = self.saldo
grid = self.grid
rows = len(grid)
columns = len(grid[0])
if x > 0:
wall = grid[y][x - 1] == 1
if wall:
if saldo > 0:
neighbors.append(Node(x - 1, y, saldo - 1, grid))
else:
neighbors.append(Node(x - 1, y, saldo, grid))
if x < columns - 1:
wall = grid[y][x + 1] == 1
if wall:
if saldo > 0:
neighbors.append(Node(x + 1, y, saldo - 1, grid))
else:
neighbors.append(Node(x + 1, y, saldo, grid))
if y > 0:
wall = grid[y - 1][x] == 1
if wall:
if saldo > 0:
neighbors.append(Node(x, y - 1, saldo - 1, grid))
else:
neighbors.append(Node(x, y - 1, saldo, grid))
if y < rows - 1:
wall = grid[y + 1][x]
if wall:
if saldo > 0:
neighbors.append(Node(x, y + 1, saldo - 1, grid))
else:
neighbors.append(Node(x, y + 1, saldo, grid))
return neighbors
def answer(maze):
rows = len(maze)
columns = len(maze[0])
source = Node(0, 0, 1, maze)
queue = deque([source])
distance_map = {source: 1}
while queue:
current_node = queue.popleft()
if current_node.x == columns - 1 and\
current_node.y == rows - 1:
return distance_map[current_node]
for child_node in current_node.get_neighbors():
if child_node not in distance_map.keys():
distance_map[child_node] = distance_map[current_node] + 1
queue.append(child_node)
and this description was there along with solution Basically you need
a breadth-first search with a minor tweak:
each node represents a cell in the grid (x- and y-coordinates), each
node knows its "saldo" (how many walls it may penetrate). What comes
to that saldo, if a node has zero saldo, it may not generate those its
neighbors that are occupied by wall. If saldo is s>0s>0, and the node
has a wall neighbor node uu, uu is generated with saldo s−1s−1.
The rest is breadth-first search: as soon you remove the exit node
from the queue, just print its distance from the starting node:
But even after a long effort i am unable to understand what "saldo"
means here and how it affects the result
i did not undertand the logic of its use
you are allowed to remove one wall as part of your remodeling plans.
Apparently, the variable saldo represents the number of walls you can remove during your escape.
It is decremented when you remove a wall; tests are made to assert whether you are still allowed to remove a wall.

Iterator for each item in a 2D Python list and its immediate m by n neighbourhood

I need to generate an iterator which will iterate over a Python 2D array and yield each item and all the items around it in an MxN neighbourhood.
For instance, given a list of 0s and 1s in a checkerboard pattern, I need an iterator object that would yield a 3x3 neighbourhood such as:
[0,1,0],
[1,0,1],
[0,1,0]
N.B. The yield does not need to be another array, but it would be nice to be able to refer to neighbours with positions/indexes relative to the central item, or at least relative to each other.
Thanks in advance.
Edit: So far I have been trying to do it solely by index, i.e.
for x in range(len(S)):
for y in range(len(S[0])):
for i in range(-1,2):
for j in range(-1,2):
#access neighbour with S[x+i][y+j]
board = [
[1,0,1,0,1],
[1,0,1,0,1],
[1,0,1,0,1],
[1,0,1,0,1],
[1,0,1,0,1]
]
def clamp(minV,maxV,x):
if x < minV:
return minV
elif x > maxV:
return maxV
else:
return x
def getNeighbour(grid,startx,starty,radius):
width = len(grid[starty])
height = len(grid)
neighbourhood = []
for y in range(clamp(0,height,starty-radius),clamp(0,height,starty+radius)+1):
row = []
for x in range(clamp(0,width,startx-radius),clamp(0,width,startx+radius)+1):
if x != startx or (x==startx and y != starty):
row.append(grid[y][x])
neighbourhood.append(row)
return neighbourhood
Examples:
>>> pprint(getNeighbour(board, 0, 0, 1))
[0]
[1, 0] (expected)
>>> pprint(getNeighbour(board, 2, 2, 1))
[0, 1, 0]
[0, 0]
[0, 1, 0] (expected)
>>>
Addressing the performance aspect with a list like:
board = [[1,0]*2000]*1000
The run time is essentially the same as if the board were 10x10
You can often get fast access of 2 dimensional array by storing the elements in a one dimensional list and computing offset bases on the logical width and height of array. It can often simplify calculations and bounds-checking, which only have to deal with one dimension internally.
class Board(object):
def __init__(self, width, height):
self.height = height
self.width = width
self.size = width*height
self.board = [i%2 for i in xrange(self.size)] # checkerboard init
def __getitem__(coords):
""" get board[x, y] """
offset = coords[1]*self.width + coords[0]
return self.board[offset]
def __setitem__(coords, value):
""" set board[x, y] = value """
offset = coords[1]*self.width + coords[0]
self.board[offset] = value
def __str__(self):
lines = []
for y in xrange(self.height):
offset = y*self.width
row = self.board[offset:offset+self.width]
lines.append(','.join(str(v) for v in row))
return ',\n'.join(lines)
def neighbourhood(self, x, y):
position = y*self.width + x
for offset in [
position-self.width-1, position-self.width, position-self.width+1,
position-1, position+1,
position+self.width-1, position+self.width, position+self.width+1]:
if -1 < offset < self.size:
yield self.board[offset]
board = Board(5, 5)
print board
print
print [value for value in board.neighbourhood(0, 0)]
print [value for value in board.neighbourhood(2, 2)]
Output:
0,1,0,1,0,
1,0,1,0,1,
0,1,0,1,0,
1,0,1,0,1,
0,1,0,1,0
[1, 0, 1, 0]
[0, 1, 0, 1, 1, 0, 1, 0]

Categories

Resources