Optimize For loop for speed python - python

Like pointed out, let me explain a bit of the code. I have 2 for loops because i have a board of a game called Tablut. I have to check from the pawn position (row_index and column_index) if there is an obstacle on the left and on the right. So, the first loop take the row_index of the pawn, then check for every row on the right side and stop when it finds an obstacle. Then do the same for the left side. If i use a single loop i don't have a way to stop searching in one direction if found an obstacle and start looking in the other, because the break will stop the entire for. The loop are used to generate all the possible actions that a move can do inside the board in that turn. Action(Position(....)) is one action that the pawn can make.
So, basically, i check in the right and left direction, if the next tile is empty i can move there, so i create a new action and add it to a list of all the possible moves, if there is an obstacle, since i can't jump it, i stop the loop.
i have this for loop and i would like to optimize it for speed.
for row in range(row_index - 1, -1, -1):
if bitboard_util.get_bit(obstacle_bitboard, row, column_index) != 1:
action = Action(Position(row_index, column_index), Position(row, column_index), color)
all_available_moves_for_pawn.append(action)
else:
break
for row in range(row_index + 1, 9):
if bitboard_util.get_bit(obstacle_bitboard, row, column_index) != 1:
action = Action(Position(row_index, column_index), Position(row, column_index), color)
all_available_moves_for_pawn.append(action)
else:
break

You could optimize this a little by preparing a structure based on your obstacle bits that will give the range of available "moves" for each position. You can prepare that structure once and reuse it multiple times to obtain the move lists directly for all column_index/row_index afterward.
from itertools import accumulate
rowCount = 9
colCount = 9
obstacleRanges = []
for col in range(colCount):
obstacles = [-1] + [ row*(bitboard_util.get_bit(obstacle_bitboard, row, col) == 1) for row in range(rowCount) ] + [rowCount]
prevObstacle = [*accumulate(obstacles,max)]
nextObstacle = [*accumulate(obstacles[::-1],lambda p,o:[p,o][o>0])][::-1]
obstacleRanges.append([*zip(prevObstacle[1:-1],nextObstacle[1:-1])])
obstacleRanges will contain tuples with the position (row) of the previous and next obstacles for every position on the board. This is computed once for the whole board and allows you to find the range of moves directly without additional calls to get_bit
usage:
prevObstacle,nextObstacle = obstacleRanges[column_index,row_index]
for moveRow in range(prevObstacle+1, nextObstacle):
action = Action(Position(row_index, column_index), Position(moveRow, column_index), color)
all_available_moves_for_pawn.append(action)
Note that this may or may not improve performance depending on the distribution and number of you pawns and also on how many times you query the actions with the same obstacle layout. For example, if your pawns ARE the obstacles and they are moving, it would be preferable to update the structure with the effect of pawn movements rather than recalculate it completely.

Related

Visualize Pathfinding Algorithm

I was doing a pathfinding visualizer in pygame and I pretty much finished but there's still one thing that I do not like about the algorithm part of it and it's the fact that when you press the visualize algorithm button it shows you the shortest path in yellow and all of the nodes the algorithm has visited ever in light blue but it shows you instantaneously and I want it to color the nodes accordingly step by step to actually reach the effect of visualizing (like in here https://clementmihailescu.github.io/Pathfinding-Visualizer/#), I tried to write some code in the function that seemed like it would have worked as intended but it didn't, here is the code:
# Breadth First Search Algorithm
def bfs(graph, start, goal):
explored = []
# Queue for traversing the
# graph in the BFS
queue = [[start]]
# If the desired node is
# reached
if start == goal:
return
# Loop to traverse the graph
# with the help of the queue
while queue:
path = queue.pop(0)
node = path[-1]
y, x = node
# Codition to check if the
# current node is not visited
if node not in explored and nodes_rows[x][y].color is not BLACK:
nodes_rows[x][y].color = LIGHT_BLUE
neighbours = graph[node]
# Loop to iterate over the
# neighbours of the node
for neighbour in neighbours:
new_path = list(path)
new_path.append(neighbour)
queue.append(new_path)
# Condition to check if the
# neighbour node is the goal
if neighbour == goal:
new_path.remove(start)
new_path.remove(goal)
return new_path
explored.append(node)
return None
The nodes_rows[x][y].color == color_name is the code that is responsible for coloring nodes on the grid which is represented by a dictionary(I provided it so it's gonna be easier for you to understand how coloring works in general in my program). The problem with that implementation is when I do add the coloring part at if statement to color all the neighbors it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node, my question is can I do it so it colors them each iteration rather than all at once by adding something to this code and not writing a new one and if I do need to write a new one that what is the instructions how can I do so?
Here is what I mean by coloring all at once like it does for now:
https://cdn.discordapp.com/attachments/772816508015083552/832303260911272046/PowerPoint_-_1_2021-04-15_20-13-35_Trim.mp4
Edit:
try:
while True:
if not ticks or pygame.time.get_ticks() - ticks >= 500:
ticks = pygame.time.get_ticks()
nodes = next(algorithm)
if nodes_rows[nodes[-1][1]][nodes[-1][0]].color != BLUE:
nodes_rows[nodes[-1][1]][nodes[-1][0]].color = LIGHT_BLUE
pygame.display.update()
except StopIteration:
pass
Tried doing it with yield and if I print it it does yield a list every half a second with a new explored node at the end like intended but it updates it all at once after waiting total amount of ticks I tried playing with the indent of display.update() but didn't work either I don't even know what to do at this point
Thanks to everyone contributing to help <3
Per the comments above, here is a simple example of a generator to help you grasp the idea.
def counter(x):
for i in range(x):
yield i
c = counter(3)
In: next(c)
Out: 0
In: next(c)
Out: 1
In: next(c)
Out: 2
What's happening is that every time you call next, the function will continue to run until it reaches the next yield, at which point it will return the yielded value.
It will also remember where it left off, so the next time you call next, the function will continue where it left off.
In your application, this could be used to yield the list of explored locations, then draw those locations in whatever color you like, call next(bfs) to step forward and yield the next list of explored locations, and so on until of course you find the solution and run out of items to yield.
One more example, perhaps a little more closely related to what you are trying to do:
def make_path():
path = []
i = 0
while True:
path.append(i)
i += 1
yield path
c = make_path()
for _ in range(6):
print(next(c))
Out: [0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
i think the problem is that you do not update the canvas in your while queue loop.
the program will execute your bfs algorithm and then it will update the canvas.
i honestly don't use pygame very regularly, but i think to force the canvas to repaint you need to stick a pygame.display.update() inside your while loop.
Pygame is not my forte but, I think there are some missing things in your project:
1.- You don't really control when colors are shown on your screen. This can be achieved with this:
pygame.display.update()
Documentation: https://devdocs.io/pygame/ref/display#pygame.display.update
Usually when working with graphics, you add a color, and when then next screen painting update comes you see it, what you must do is to force the painting so you can animate safely.
2.- Coloring animations are not being done by default.
I couldn't help noticing this in your initial question: "it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node". If you want an animation like the example linked above (Pathfinding visualizer), I'm afraid you have to do it manually. Try creating a test with a white square and start painting growing circles inside interpolating colors, until you reach the walls and paint the full square. This should be similar to what are you trying to accomplish.
3.- Perhaps you should add some stops when you are animating your canvas.
The refresh rate of your hardware is way too faster than your human eye.
To properly see a coloring animation you should try adding execution stops, when interpolating colors.
nodes_rows[x][y].color = Color(r, g, b) # Start with a color and interpolate incrementing or decrementing r, g and b until your reach a final color.
pygame.display.update() # Update your screen
pygame.time.wait(1000) # In milliseconds, add some time wait so screen can be painted, if time is long enough perhaps screen updates meanwhile and you do not need to update the screen.
4.- (Optional) Parallel painting: Using threads.
If this does not work right for you, add threads.
As I stated before, pygame is not my forte, but consider adding threads to animate each square color. I've found a post here explaining this.
using threading in pygame
I hope this helps you and have a happy coding experience! :D

Find the possible slide downs in a pyramid

I'm losing my mind with this codewars/project euler problem: https://www.codewars.com/kata/551f23362ff852e2ab000037/train/python
I need to find the max sum in all of the possible pyramid slide downs, and as of right know I'm trying to calculate the possible slide downs that exist in the pyramid. It works fine but for integers bigger than 25 the function begins to be really slow.
My code is this:
def find_seq(x):
'''
input: x, positive integer greater than 1
Returns an array with the slides from the top to the bottom of
an x floors high pyramid
'''
list_slides = []
if x == 2:
return [[0,0],[0,1]]
else:
prev_slides = find_seq(x-1)
for el in prev_slides:
list_slides.append([0]+el)
for el in prev_slides:
list_slides.append([0]+list(i+1 for i in el))
return list_slides
I can see that for each new floor the calculating time grows exponentially but I can't think of any other way to adress the problem.
tl;dr: Go from the bottom up for linear complexity.
Well, you are right it grows exponentially.
The problem is not your code, but your direction.
Let's look at the pyramid from bottom up - you can see right away that if you are at the second to last layer and you want to slide down, you will choose the path that is directly under you and has the larger value, i.e you can slide only left or right and the bigger will be better.
Now, going up to the third to last, you only need to find the route down to the floor below which is the best (summing up the value of the bottom floor of course).
keep going like that to the top of the pyramid and by the end, you get the value of the best route, or longest slide.
code:
def longest_slide_down(pyramid):
if len(pyramid) == 1:
return pyramid[0][0]
last_layer = pyramid[-1]
add_layer = []
for i in range(1, len(last_layer)):
add_layer.append(max(last_layer[i], last_layer[i-1]))
pyramid[-2] = [a+b for a, b in zip(pyramid[-2], add_layer)]
return longest_slide_down(pyramid[:-1])
And for the efficiency seekers out there, a numpyed version of the same code:
import numpy as np
def longest_slide_down(pyramid):
if len(pyramid) == 1:
return pyramid[0][0]
pyramid = np.array(pyramid)
pyramid[-2] += np.maximum(pyramid[-1][:-1], pyramid[-1][1:])
return longest_slide_down(pyramid[:-1])

Pygame: trying to get the whole sprite to cross a boundary instead of just the top left pixel

I am trying to make my sprite move according to the astar pathfinding algorithm. However, after I implemented it, I realised that the movement of the sprite is only in accordance to the top left pixel. That means that if the algorithm tells it to move up after crossing a boundary, it will do so once the top left pixel crosses that boundary. However, this means that the entire sprite has not actually crossed, resulting in a collision if there is an obstacle just above it. Is there any way to tell it to move left more before moving up]1
def astar_ghost(pac,ghost):
maze=astar.create_maze(screen_width,screen_height,obstacles) #creates a maze of 0s and 1s. The 1s represent the obstacles
start=(ghost.gridloc[0],ghost.gridloc[1])
end=(ghost.goal_node[0],ghost.goal_node[1])
goal_node=astar.astar(maze,start,end)
if goal_node==None:
pass
else:
ghost.goal_node=goal_node
game=True
if ghost.goal_node[0]<ghost.gridloc[0]:#left
print('move left')
game=collision(pac,ghost) #collision is another function that checks for collision and returns True or False. If False, the game will be over
ghost.left=True
ghost.right=False
ghost.up=False
ghost.down=False
elif ghost.goal_node[0]>ghost.gridloc[0]:#right
print('move right')
game=collision(pac,ghost)
ghost.left=False
ghost.right=True
ghost.up=False
ghost.down=False
elif ghost.goal_node[1]<ghost.gridloc[1]:#up
print('move up')
game=collision(pac,ghost)
ghost.left=False
ghost.right=False
ghost.up=True
ghost.down=False
elif ghost.goal_node[1]>ghost.gridloc[1]:#down
print('move down')
game=collision(pac,ghost)
ghost.left=False
ghost.right=False
ghost.up=False
ghost.down=True
You are asking a few different questions here. I'll answer here to what I think you're trying to ask: Is there a way to check if an entire sprite has crossed a boundary, instead of just the top-left corner?. So, my answer (note this will only work if your boundary line is linear): You need to check each of the corners individually, then, if all of them have returned True, then you move on. Example:
def collision(sprite1, boundary):
def internal_collision(point, boundary):
... # The actual math happens here, returns True/False
corners = []
for h in [0, 1]:
for j in [0, 1]:
corners.append([sprite1.rect.x+(h*sprite1.rect.width),
sprite1.rect.y+(j*sprite1.rect.height)])
corner_check = []
for corner in corners:
corner_check.append(internal_collision(corner, boundary))
return all(corner_check)
I don't know how your code works, so I've tried to keep this as malleable and understandable as possible, so you can re-implement it in your own code.

Shorten repetitive code with a For Loop to increment variables.

I am drawing a blank, but I do think that writing this code manually seems excessive. Can I use a 'for loop' to shorten this and increment the variables below?
pos = 1000
m1.setAsHome() # set position as zero for all of the commands
m1.goTo(pos) # move to the original position oof the limit switch
while(m1.isBusy()):
continue
m1.free() # reset the motor
while(m2.isBusy()):
continue
m2.setAsHome() # set position as zero for all of the commands
m2.goTo(pos) # move to the original position oof the limit switch
while(m2.isBusy()):
continue
m2.free() # reset the motor
My hunch is something along the lines of this:
for i in range(4):
m = 0
print m[i].setAsHome()
of course this yields an error. Sorry for the newbie question, but I am sure there must be a way to shorten this. Furthermore, the code above keeps going to include 4 motors. Thank you.
You need a list of motors:
motors = [m1, m2, m3, m4]
So you can use a for loop:
for motor in motors:
motor.setAsHome()
motor.goTo(pos)

Creating a list of dictionaries where each dictionary contains another dictionary as values

Im writing an algorithm in Python which plays this game.
The current state of the board of tiles in the game is a dictionary in the form of:
{
<tile_id>: {
'counters': <number of counters on tile or None>,
'player': <player id of the player who holds the tile or None>,
'neighbours': <list of ids of neighbouring tile>
},
...
}
I have another dictionary which stores all of my tiles which are 'full' (i.e. a tile which has one less counter than its number of neighbours and where the player is me) This dictionary, full_tiles, is in the same form as the board dictionary above.
I am now trying to create a list, chains, where each element in the list is a dictionary of my full tiles that are neighbouring at least one other full tile (i.e a chain of full tiles). So this will be a list of all my seperate chains on the board.
Here is my code so far:
for tile_id, tile in full_tiles.items(): #iterates through all full tiles
current_tile = {tile_id : tile} #temporarily stores current tile
if not chains: #if chains list is empty
chains.append(current_tile) #begin list
else: #if list is not empty
for index, chain in enumerate(chains): #iterate though list of chains
if not (current_tile in chain): #if current tile is not in current chain
for tile_id2, tile2 in chain.items(): #iterate through tiles in current chain
for neighbour in tile2["neighbours"]: #iterate through each neighbour of current tile
#neighbour is in the form of tile_id
if neighbour in chain: #if current tile's neighbour is in chain
chain[tile_id] = tile #add tile to chain
It is very difficult for me to test and debug my code and check if it is working correctly as the code can only be run in an application that simulates the game. As you can see, there is quite a lot going on in this block of code with all of the nested loops which are difficult to follow. I cannot seem to think straight at the minute and so I cannot determine if this mess, in all honesty, will function as I hope.
While I am writing this, I have just realised that - on line 7 of this code - I am only checking if the current tile is not in the current chain and so there will be intersecting chains which, of course, will be a mess. Instead of this, I need to first check if the current tile is in not in any of the chains, not just one.
Apart from this error, will my code achieve what I am attempting? Or can you recommend a simpler, neater way to do it? (There has to be!)
Also, let me know if I have not given enough information on how the code is run or if I need to explain anything further, such as the contents of the board dictionary.
Thank you for any help in advance.
EDIT: Unfortunately, I was under a time constraint to complete this project, and as it was my first time ever working with Python, I currently lack the knowledge in the language to optimise my solution using the sources given below. Here is my final extremely ugly and messy solution to this problem which, in the end, worked fine and wasn't terribly inefficient given the small data set that the code works on.
for x in range(0, len(my_hexplode_chains) - 1):
match_found = False
for y in range(x + 1, len(my_hexplode_chains)):
for tile_id_x, tile_x in my_hexplode_chains[x].items(): #compare each chain in list
for tile_id_y, tile_y in my_hexplode_chains[y].items(): #to every other chain
for neighbour in tile_x["neighbours"]: #if tiles in different lists
if neighbour == tile_id_y: #are neighbours
match_found = True
my_hexplode_chains[x].update(my_hexplode_chains[y]) #append one chain to the other
del my_hexplode_chains[y] #delete appended chain
if match_found: #continue loop at next chain
break #very ugly way to do this
if match_found:
break
if match_found:
break
if match_found:
break
How about this optimization?
def find_match (my_hexplode_chains):
x = 0
len_chain = len(my_hexplode_chains)
while x <= len_chain:
y = x + 1
for tile_id_x, tile_x in my_hexplode_chains[x].items():
for tile_id_y, tile_y in my_hexplode_chains[y].items():
if tile_id_y in tile_x["neighbours"]:
my_hexplode_chains[x].update(my_hexplode_chains[y])
del my_hexplode_chains[y]
return True
x += 1
return False
You could pass this function after each move in your game and trace the output.

Categories

Resources