I've been working on some introductory Graph Theory lately, and came across the following problem statement:
Given a 2D matrix as input, representing a maze (where 'E' is the start of the matrix and 'S' is the end), find the length of the shortest path from E to S. Walls in the maze are represented by '#', while accessible spaces are indicated with '.'. It's also a given that the outer edge of the matrix is covered with walls. If no path exists from E to S, return -1.
Since the graph isn't weighted, I tried implementing a BFS algorithm, using deque. However, when the mazes start hitting around 500x500, execution time hits 10s, and when I try to go up to 1000x1000, it's obviously a lot worse.
Here's my code:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return tuple([x, y])
start = find_start(maze)
queue = deque([[start]])
seen = {start}
while queue:
path = queue.popleft()
x, y = path[-1]
if maze[y][x] == endchar:
return len(path)-1
for (x2, y2) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 < x2 < width-1 and 0 < y2 < height-1 and maze[y2][x2] != wall and (x2, y2) not in seen:
queue.append(path + [(x2, y2)])
seen.add((x2, y2))
return -1
I found some very useful answers on the site so far, but none of the current ones seem to give any other optimizations that I haven't implemented yet...
Thanks!
EDIT: Thanks to the lovely person who edited my question to make the key words pop :). Here's an example of a matrix that you can run the algorithm on:
#####
#E#S#
#.#.#
#...#
#####
This should return the value 6.
EDIT2: Fixed some small mistakes.
As suggested in the comments, you don't have to store the paths. Try this:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return (x, y, 0)
start = find_start(maze)
queue = deque([start])
seen = set()
while queue:
x, y, d = queue.popleft()
if not 0 <= x < width:
continue
if not 0 <= y < height:
continue
if maze[y][x] == wall:
continue
if maze[y][x] == endchar:
return d
if (x, y) in seen:
continue
seen.add((x, y))
queue.append((x+1, y, d+1))
queue.append((x-1, y, d+1))
queue.append((x, y+1, d+1))
queue.append((x, y-1, d+1))
return -1
maze = [x for x in """
#####
#E#S#
#.#.#
#...#
#####
""".split('\n') if x]
print shortest_path_1(maze)
Related
I made a Fourier Series/Transform Tkinter app, and so far everything works as I want it to, except that I am having issues with the circles misaligning.
Here is an image explaining my issue (the green and pink were added after the fact to better explain the issue):
I have narrowed down the problem to the start of the lines, as it seems that they end in the correct place, and the circles are in their correct places.
The distance between the correct positions and the position where the lines start seems to grow, but is actually proportional to the speed of the circle rotating, as the circle rotates by larger amounts, thus going faster.
Here is the code:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
I am aware that this is not the best way to achieve what I am trying to achieve, as using classes would be much better. I plan to do that in case nobody can find a solution, and hope that when it is re-written, this issue does not persist.
The main problem that you are facing is that you receive floating point numbers from your calculations but you can only use integers for pixels. In the following I will show you where you fail and the quickest way to solve the issue.
First your goal is to have connected lines and you calculate the points here:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
when you add the following code into this function you see that it fails there.
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
Because x and y should always match with the last point (end of the previous line) that will be endCoord[i-1][0] and endCoord[i-1][1].
to solve your problem I simply skipt the match for the sarting point of the follow up lines and took the coordinates of the previous line with the following alternated function:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
Additional proposals are:
don't use wildcard imports
import just what you really use in the code random isnt used in your example
the use of global in the global namespace is useless
create functions to avoid repetitive code
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
use .after(ms,func,*args) instead of a interrupting while loop and blocking call time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
read the PEP 8 -- Style Guide for Python
use intuitive variable names to make your code easier to read for others and yourself in the future
list_of_radii = [200] #instead of rList
as said pixels will be expressed with integers not with floating point numbers
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
using classes and a canvas for each animation will become handy if you want to show more cycles
dont use tkinters update method
As #Thingamabobs said, the main reason for the misalignment is that pixel coordinates work with integer values. I got excited about your project and decided to make an example using matplotlib, this way I do not have to work with integer values for the coordinates. The example was made to work with any function, I implemented samples with sine, square and sawtooth functions.
I also tried to follow some good practices for naming, type annotations and so on, I hope this helps you
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()
I have a clicking applicaiton on a phone.
I want to sample last N points so it won't click the same "place" over and over again.
I guess it should be kind of this:
This I want to avoid.
I guess the circlue center should be the center of all points ?
How to determind the radius ?
I think calculation of the last N dots can be used to calculte the "new" N dots once a new click is done, to reduce performance.
Any suggestions ?
Thanks
def clicking_loop_protection(self, x, y, methods):
'''
:param x:
:param y:
:param method: 'xpath'/'point' it can be both
:return:
'''
def centroid(points):
_len = len(points)
x_coords = [p[0] for p in points]
y_coords = [p[1] for p in points]
centroid_x = sum(x_coords) / _len
centroid_y = sum(y_coords) / _len
return centroid_x, centroid_y
def calculate_points_distance(x1, y1, x2, y2):
dist = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
return dist
point_res = True
if 'point' in methods:
# add current point
_point = (x, y)
try:
self.points_history[self.points_history_index] = _point
except IndexError:
self.points_history.append(_point)
if len(self.points_history) == self.points_history_length:
centroid_point_x, centroid_point_y = centroid(self.points_history)
radius = self.displayWidth/10
for point in self.points_history:
# distance from centroid should be less than radius (to fail the test)
distance = calculate_points_distance(centroid_point_x, centroid_point_y, point[0], point[1])
if distance > radius:
point_res = True # pass test !
break
else:
point_res = False
self.points_history_index += 1
self.points_history_index %= self.points_history_length
return xpath_res and point_res
Given a maze, start coordinate, and end coordinate,
maze = [
[BLACK, BLACK, WHITE, WHITE],
[BLACK, WHITE, WHITE, WHITE],
[WHITE, WHITE, BLACK, WHITE],
[WHITE, WHITE, BLACK, WHITE],
]
s = Coordinate(3, 0)
e = Coordinate(0, 3)
I am trying to find the path from start to end using BFS.
Finding the path is straightforward, but I am struggling with keeping the path to the destination.
What I've tried is
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
path = []
while queue:
curr = queue.popleft()
if curr == e:
path.append(curr)
return path
path.append(curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
Something like this, but the result prints out all the nodes that I've visited, instead of the final path. Any tips on keeping the right path and removing the node that does not belong to the final path?
Instead of maintaining a path list, you can maintain an edge_to dictionary which keeps track of which previous vertex led to visiting a certain vertex. Whenever you add something to your queue, you can can update edge_to. A modified version of your function using this approach is as follows:
Coordinate = collections.namedtuple('Coordinate', ['x', 'y'])
def find_path(s, e):
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
edge_to = {s: None}
while queue:
curr = queue.popleft()
if curr == e:
return path(edge_to, curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
c = Coordinate(new_x, new_y)
edge_to[c] = curr
queue.append(c)
Notice the call to path(...) when you find your end vertex. That function just builds a list from the edge_to dictionary:
def path(edge_to, end):
curr = end
res = []
while curr != None:
res.append(curr)
curr = edge_to[curr]
return list(reversed(res))
For your given maze, and start and end coordinates, we get the following output:
s = Coordinate(3, 0)
e = Coordinate(0, 3)
print(find_path(s, e))
Output
[Coordinate(x=3, y=0), Coordinate(x=2, y=0), Coordinate(x=2, y=1), Coordinate(x=1, y=1), Coordinate(x=1, y=2), Coordinate(x=0, y=2), Coordinate(x=0, y=3)]
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
your queue.append(Coordinate(new_x, new_y)) is running everytime your for loops iterates.
We want this condition to only happen when our if condition runs, so lets try something like this:
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
queue.append(Coordinate(new_x, new_y))
continue
When our if-condition is met, append it, and then continue. Let me know if this helps.
I started working on python for AI and I'm having some problems:
I have an n- Queens problem here is a detailed explanation of the problem
The fitness function receives a an array of the form:
decoded = [3, 1, 2, 5 ... n]
where the element corresponds to the X coordinate and the index corresponds to the Y coordinate
i.e. taking the coordinates from the example above:
# [X, Y]
pairCoords = [[3,0], [1, 1], [2, 2], [5, 1], ... [n, z]]
so I have the fitness function which recieves a similar array to the first example.
var record starts with the max number of collisions n(n-1)* and decreases with each collision found
def fitness(self, decodedGenes):
record = self.numeroN * (self.numeroN-1)
for y in range(len(decodedGenes)):
if self.positionIsAtacking(decodedGenes, decodedGenes[y], y):
record = record - 1
return record
so a best case world return n(n-1)* and a worst case would return 0
the auxiliar function it calls checks a given X and Y coordinates and returns if there is a collision but it's not working
def positionIsAtacking(self, coords, X, Y):
for i in range(len(coords)):
# Check Y
if (coords[i] == Y):
return True
# Check Diagonals
if (coords[i] - Y == i - X):
return True
if (coords[i] - Y == X - i):
return True
return False
I have tried changing the parameters but I do not know where to search anymore I think the second function doesn`t work or maybe y changed x and y
def fitness(self, cromosoma):
record = self.numeroN * (self.numeroN - 1)
for row in range(len(board)):
decodedGenes.append(self.decodeGene(board[row]))
for y in range(len(decodedGenes)):
x = decodedGenes[y]
record = record - self.collisions(decodedGenes, x, y)
return record
def collisions(self, coords, X, Y):
board = []
r = 0
for i in range(len(coords)):
board.append([0] * self.numeroN)
for y in range(len(coords)):
board[y][coords[y]] = 1
for y in range(len(board)):
for x in range(len(board)):
# if has Queen and is not the same
if board[y][x] == 1 and y != Y:
# check x
if x == X:
r = r + 1
# check Diagonals
if self.crash_diagonal(x, y, X, Y):
r = r + 1
return r
def crash_diagonal(self, x1, y1, x2, y2):
dx = abs(x1 - x2)
dy = abs(y1 - y2)
return dx == dy
I have this programme to discuss and I think its a challenging one.. Here I have a yml file which contains the data for an image. The image has x,y,z values and intensity data which is stored in this yml file. I have used opencv to load the data and its working fine with masking.. but I am having problems in dynamically appending the masks created.. Here is the code I made for solving the problem :
import cv
from math import floor, sqrt, ceil
from numpy import array, dot, subtract, add, linalg as lin
mask_size = 9
mask_size2 = mask_size / 2
f = open("Classified_Image1.txt", "w")
def distance(centre, point):
''' To find out the distance between centre and the point '''
dist = sqrt(
((centre[0]-point[0])**2) +
((centre[1]-point[1])**2) +
((centre[2]-point[2])**2)
)
return dist
def CalcCentre(points): # Calculates centre for a given set of points
centre = array([0,0,0])
count = 0
for p in points:
centre = add(centre, array(p[:3]))
count += 1
centre = dot(1./count, centre)
print centre
return centre
def addrow(data, points, x, y, ix , iy ):# adds row to the mask
iy = y + 1
for dx in xrange(-mask_size2 , mask_size2 + 2):
ix = x + dx
rowpoints = addpoints(data, points, iy, ix)
return rowpoints
def addcolumn(data, points, x, y, ix , iy ):# adds column to the mask
ix = x + 1
for dy in xrange(-mask_size2-1 , mask_size2 + 1):
iy = y + dy
columnpoints = addpoints(data, points, iy, ix)
return columnpoints
def addpoints (data, points, iy, ix): # adds a list of relevant points
if 0 < ix < data.width and 0 < iy < data.height:
pnt = data[iy, ix]
if pnt != (0.0, 0.0, 0.0):
print ix, iy
print pnt
points.append(pnt)
return points
def CreateMask(data, y, x):
radius = 0.3
points = []
for dy in xrange(-mask_size2, mask_size2 + 1): ''' Masking the data '''
for dx in xrange(-mask_size2, mask_size2 + 1):
ix, iy = x + dx, y + dy
points = addpoints(data, points, iy , ix )
if len(points) > 3:
centre = CalcCentre(points)
distances = []
for point in points :
dist = distance(centre, point)
distances.append(dist)
distancemax = max(distances)
print distancemax
if distancemax < radius: ''' Dynamic Part of the Programme'''
#while dist < radius: # Going into infinite loop .. why ?
p = addrow(data, points, x, y, ix , iy )
q = addcolumn(data, points, x, y, ix , iy )
dist = distance(centre, point) # While should not go in infinite
#loop as dist is changing here
print dist
print len(p), p
print len(q), q
points = p + q
points = list(set(points)) # To remove duplicate points in the list
print len(points), points
def ComputeClasses(data):
for y in range(0, data.height):
for x in range(0, data.width):
CreateMask(data, y, x)
if __name__ == "__main__":
data = cv.Load("Z:/data/xyz_00000_300.yml")
print "data loaded"
ComputeClasses(data)
Feel free to suggest alternative methods/ideas to solve this problem.
Thanks in advance.