Weighted Traversal Algorithm (Breadth first is better?) - python

I'm having trouble designing an algorithm for a traversal problem.
I have a Ship that I control on a 2D grid and it starts on the very bottom of the grid. Each tile of the grid has a value (between 0 and 1000) equal to how much 'resource' is in that tile.
The Ship can go_left(), go_up(), go_right() or stay_still()
If the ship stay_still() it collects 25% of it's current tile's resource (rounded up to the nearest int).
If the ship uses a move command, it needs to spend 10% of it's current tile resource value rounded down. Moves that cost more than the ship has collected are illegal. (So if a ship is on a 100, it costs 10 to move off the 100, if it's on a 9 or less, moving is free).
The goal is to find a relatively short path that legally collects 1000 resource. Returning a list of the move order to corresponds to the path.
I naturally tried a recursive approach:
In sudo-code the algorithm is:
alg(position, collected, best_path):
if ship has 1000:
return best_path
alg(stay still)
if ship has enough to move:
alg(try left)
alg(try up)
alg(try right)
If you want a closer look at the actual syntax in python3 here it is:
def get_path_to_1000(self, current_position, collected_resource, path, game_map):
if collected_resource >= 1000:
return path
path_stay = path.copy().append(stay_still())
self.get_path_to_1000(current_position, collected_resource +
math.ceil(0.25 * game_map[current_position].value),
path_stay, game_map.copy().collect(current_position))
cost = math.floor(0.1 * game_map[current_position].value)
if collected_resource >= cost:
direction_list = [Direction.West, Direction.North, Direction.East]
move_list = [go_left(), go_up(), go_right()]
for i in range(3):
new_path = path.copy().append(move_list[i])
self.get_path_to_1000(
current_position.offset(direction_list[i]),
collected_resource - cost, new_path, game_map)
The problem with my approach is that the algorithm never completes because it keeps trying longer and longer lists of the ship staying still.
How can I alter my algorithm so it actually tries more than one option, returning a relatively short (or shortest) path to 1000?

The nature of this problem (ignoring the exact mechanics of the rounding down/variable cost of moving) is to find the shortest number of nodes in order to acquire 1,000 resources. Another way to look at this goal is that the ship is trying to find the most efficient move with each turn.
This issue can be solved with a slightly modified version of Dijksta's algorithm. Instead of greedily choosing the move with the least weight, we will choose the move with the most weight (greatest number of resources), and add this value to a running counter that will make sure that we reach 1000 resources total. By greedily adding the most efficient edge weights (while below 1000), we'll find the least number of moves to get a total 1000 resources.
Simply keep a list of the moves made with the algorithm and return that list when the resource counter reaches 1000.
Here's a helpful resource on how to best implement Dijkstra's algorithm:
https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
With the few modifications, it should be your best bet!

Related

Calculating a custom cost function within A* Search

Problem Background
I am working on a problem where using an A* search algorithm, I need to find the fastest route between two nodes in a graph given a specific cost function. Here is an overview of the cost function:
Package finds the fastest route, in expectation, for a certain delivery driver. Whenever this driver drives on a road with a speed limit >= 50 mph, there is a chance that a package will fall out of their truck and be destroyed. Consequently, this mistake will add an extra 2*(t_road + t_trip) hours to their trip, where t_trip is the time it took to get from the start city to the beginning of the road, and t_road is the time it takes to drive the length of the road segment.
For a road of length l miles, the probability p of this mistake happening is equal to tanh(l/1000) if the speed limit is >= 50 mph, and 0 otherwise. This means that, in expectation, it will take t_road + p * 2(t_road + t_trip) hours to drive on this road.
So, I have constructed my graph where every city in a given dataset is a node and each road that connects two cities is an edge. Each edge has dictionary of weights defined as: {'distance':int, 'speed':int, 'time':float}
Problem Overview
So, I am trying to figure out where in my A* search algorithm I am going wrong because when I try calculating the package cost function, there are some edge cases (look below for an example) where my cost function is not performing properly.
My Attempt(s)
Here is a method I wrote to calculate the package cost:
def delivery_calculation(path, G):
result = 0
for i in range(0, len(path)-1):
edge = G.get_edge(path[i], path[i+1])
if not edge:
continue
if edge[1]['speed'] >= 50:
prob = math.tanh(edge[1]['distance']/1000)
else:
prob = 0
t_road = edge[1]['time']
t_trip = total_metric(path[0:i+1], G, 'time')
result += t_road + (prob*(2*(t_road+t_trip)))
return result
where path is a list of nodes, G is a graph, and get_edge() returns an edge and its weight between two nodes. It should be noted that the graph is undirected.
So within my A*, when I check all adjacent nodes and look to add them to the fringe (implemented as a priority queue), I calculate what the package cost would be for a given adjacent node. This, to me, seems like correct logic, but I could be wrong. Here is the code for that:
for move in G.get_adjacent(curr_node):
neighbor = move[0]
weight = move[1]
path = [neighbor]
node = curr_node
while node is not None:
path.append(node)
node = tracked_nodes[node]
path.reverse()
ncost = float(delivery_calculation(path, G))
where get_adjacent() gets all nodes that are adjacent to a given node.
So ncost is then used for the priority metric within my priority queue. My thinking behind that is if I want the fastest route, the smallest package cost should lead me there.
Results
So, the problem here is that for some searches, other cost functions turn up faster routes for package costs than the package cost function itself. An example:
I want to find the fastest route between Bloomington, Indiana, and Chicago, Illinois based on time
Total hours: 3.900 (cost function)
Total hours for package delivery: 4.725
I want to find the fastest route between Bloomington, Indiana, and Chicago, Illinois based on package time
Total hours: 4.115
Total hours for package delivery: 4.736 (cost function)
I've spent hours looking at my code and trying to rework the logic, but I cannot figure out why this would happen. Potentially how the float type is working and rounding values? This is one of the only edge cases that I've found (another being Bloomington, Indiana to Buffalo, New York). A working example would look like:
I want to find the fastest route between Denver, Colorado, and Milwaukee, Wisconsin based on time
Total hours: 16.387 (cost function)
Total hours for package delivery: 33.704
I want to find the fastest route between Denver, Colorado, and Milwaukee, Wisconsin based on package time
Total hours: 21.513
Total hours for package delivery: 23.037 (cost function)
Any ideas? I am absolutely stuck. Thank you in advance and I appreciate you taking the time to read this entire post.

Saving valid moves in Negamax makes little to no difference in speed

I have a normal Negamax algorithm with alpha-beta pruning which is initiated with iterative deepening (ID). I thought that to really get use of ID I save the calculated valid moves from depth 1 in a table, so next time I go for depth 2 and the same original position arrives I can just grab the valid moves from the table instead to save time. However, I find that this idea doesn't save any time at all really which makes me think:
I have never seen anyone do this, is it not worth it for some reason?
My implementation of this is wrong?
I am confused by how Negamax works and maybe this is impossible to do in the first place?
Here is the original iterative call, along with a snippet of the Negamax function itself:
self.valid_moves_history = []
for depth in range(1, s.max_search_depth):
move, evaluation = self.negamax(gamestate, depth, -math.inf, math.inf, s.start_color)
# ----------------------------------------------------------------------------
def negamax(self, gamestate, depth, alpha, beta, color):
if self.zobrist_key in self.valid_moves_history:
children = self.valid_moves_history[self.zobrist_key]
else:
children = gamestate.get_valid_moves()
self.valid_moves_history[key] = children
if depth == 0 or gamestate.is_check_mate or gamestate.is_stale_mate:
return None, e.evaluate(gamestate, depth) * color
# Negamax loop
max_eval = -math.inf
for child in reversed(children):
gamestate.make_move(child[0], child[1])
score = -self.negamax(gamestate, depth - 1, -beta, -alpha, -color)[1]
gamestate.unmake_move()
if score > max_eval:
max_eval = score
best_move = child
alpha = max(alpha, max_eval)
if beta <= alpha:
break
The most time consuming tasks of my complete program are distributed something like this (% of total runtime for a game):
Calculate valid moves: 60%
Evaluation function (medium complexity at the moment): 25%
Negamax itself with lookups, table saves etc: 5%
Make/unmake moves: 4%
Is it normal/reasonable for the calculating move time to be this high? This is the main reason why I thought to save valid moves in a list in the first place.
Or can someone please explain why this is a good/bad idea and what I should do instead? Thank you for any input.
I know this thread is quite old at this point but I think that this could still be useful to some people. The whole topic which you are talking about is called transposition tables in Minimax and you can find many links to the topic. Negamax is the same as Minimax except you do not have separate functions for the Max and Min players, and instead you just call a max function and turn it into a negative. I think it is probably more useful for you to implement move ordering first as it can double the speed of your program. You can also find a more efficient way to find valid moves to speed up the program.

Select Weighted Items From List with Restrictions

I have a list of weights where each index represents the weight of an item.
Weights = [0.3, 0.7, 0.1, 0.3, 0.1, ...]
Each item has a list of of collision items, hence if you pick item 0 you can't pick item 1.
Item_0 = [0,1,3,7]
Item_1 = [1,5,6,8]
All the items have the same number of collisions.
The goal is to pick N items keeping in mind the collisions and maximize the weight of the items picked.
What's the easiest and most pythonic way to do this?
I initially thought a greedy approach will work (sort the weights in descending order) but it doesn't and the only other solution I can come up with is finding all possible combinations of N items (without collisions) and calculating the total weights.
Greedy Algorithm (Gives incorrect result):
def pickTop_K(weights, collision_dict):
ret = []
while len(ret) < k:
index = np.argmax(probs)
ret.append(index)
collisions = collision_dict[index]
weights[collisions] = 0
if np.max(weights) == 0:
break
return ret
I'm fairly sure that this problem is NP-hard.
However there is a way to solve it while limiting how much work you do as follows.
First, sort the vertices by descending weight.
Then do an A* search. That is, you have a priority queue that always returns the highest priority option. The priority of a set of choices is the total weight of the choices plus an upper bound on the weight of choosing the next few vertices.
In pseudocode that looks something like this.
create priority queue paths
add to paths an empty set with weight the sum of the first n items.
while paths is not empty:
take the top path from the queue.
If the path is a set of n choices:
that is your answer, return it
else:
Look for the next element that could or couldn't be added.
If you found it:
Add an option to the queue where you choose it
Add an option to the queue where you don't choose it
Else:
This is a dead end. Pass.
My best guess is that it is probably worthwhile to make the best estimate that you can of the upper limit. That is, you actually find the top vertices that are not in conflict with any existing choice and sum them to come up with a good upper bound. This operation is expensive, but reduces branching. And the exponential growth in options comes from branching - it is worth investment to prune branches early.
If the greedy choice works, this algorithm will find it very quickly. If something close to the greedy choice works, there shouldn't be too much backtracking. But if you're unlucky, this will take exponential space and memory. (But if it is actually NP-hard, you can't expect to do better than that.)

generate mouse velocity when only integer steps are allowed

I write a simple program in python which includes moving my mouse (I do his with PyUserInput).
However: It is only allowed to move the mouse in integer steps (say pixels).
So mouse.move(250.3,300.2) won't work.
I call the move function about 30 times in a second and move the mouse a few pixels. The speed with which I move the mouse varies from 0.5-2.5px/call. Rounding gives me 1-3 (move only want ints) which does not really represent the speed.
I search for a solution (maybe generator?) which takes my current speed (e.g. 0.7px) and gives me back a pattern (like a PWM Signal) out of 0 and 1 (e.g. 1,1,0,1,1,0...) which yields the 0.7px in average.
However this generator has to be adaptive because speed is constantly changing.
I am quite new to python and stuck with the last point: The variability of the generator function.
Here is what I have so far:
# for 0.75px/call
def getPWM(n):
nums = [1,0,1,1]
yield nums[n%4]
What you need to do is keep track of the previous position and the desired current position, and hand out the rounded coordinate. You could track the previous position in a function but it's much easier to do it in a class.
class pwm:
def __init__(self):
self.desired_position = 0.0
self.actual_position = 0
def nextPWM(self, speed):
self.desired_position += speed
movement = round(self.desired_position - self.actual_position)
self.actual_position += movement
return movement

Number of shortest paths

Here is the problem:
Given the input n = 4 x = 5, we must imagine a chessboard that is 4 squares across (x-axis) and 5 squares tall (y-axis). (This input changes, all the up to n = 200 x = 200)
Then, we are asked to determine the minimum shortest path from the bottom left square on the board to the top right square on the board for the Knight (the Knight can move 2 spaces on one axis, then 1 space on the other axis).
My current ideas:
Use a 2d array to store all the possible moves, perform breadth-first
search(BFS) on the 2d array to find the shortest path.
Floyd-Warshall shortest path algorithm.
Create an adjacency list and perform BFS on that (but I think this would be inefficient).
To be honest though I don't really have a solid grasp on the logic.
Can anyone help me with psuedocode, python code, or even just a logical walk-through of the problem?
BFS is efficient enough for this problem as it's complexity is O(n*x) since you explore each cell only one time. For keeping the number of shortest paths, you just have to keep an auxiliary array to save them.
You can also use A* to solve this faster but it's not necessary in this case because it is a programming contest problem.
dist = {}
ways = {}
def bfs():
start = 1,1
goal = 6,6
queue = [start]
dist[start] = 0
ways[start] = 1
while len(queue):
cur = queue[0]
queue.pop(0)
if cur == goal:
print "reached goal in %d moves and %d ways"%(dist[cur],ways[cur])
return
for move in [ (1,2),(2,1),(-1,-2),(-2,-1),(1,-2),(-1,2),(-2,1),(2,-1) ]:
next_pos = cur[0]+move[0], cur[1]+move[1]
if next_pos[0] > goal[0] or next_pos[1] > goal[1] or next_pos[0] < 1 or next_pos[1] < 1:
continue
if next_pos in dist and dist[next_pos] == dist[cur]+1:
ways[next_pos] += ways[cur]
if next_pos not in dist:
dist[next_pos] = dist[cur]+1
ways[next_pos] = ways[cur]
queue.append(next_pos)
bfs()
Output
reached goal in 4 moves and 4 ways
Note that the number of ways to reach the goal can get exponentially big
I suggest:
Use BFS backwards from the target location to calculate (in just O(nx) total time) the minimum distance to the target (x, n) in knight's moves from each other square. For each starting square (i, j), store this distance in d[i][j].
Calculate c[i][j], the number of minimum-length paths starting at (i, j) and ending at the target (x, n), recursively as follows:
c[x][n] = 1
c[i][j] = the sum of c[p][q] over all (p, q) such that both
(p, q) is a knight's-move-neighbour of (i, j), and
d[p][q] = d[i][j]-1.
Use memoisation in step 2 to keep the recursion from taking exponential time. Alternatively, you can compute c[][] bottom-up with a slightly modified second BFS (also backwards) as follows:
c = x by n array with each entry initially 0;
seen = x by n array with each entry initially 0;
s = createQueue();
push(s, (x, n));
while (notEmpty(s)) {
(i, j) = pop(s);
for (each location (p, q) that is a knight's-move-neighbour of (i, j) {
if (d[p][q] == d[i][j] + 1) {
c[p][q] = c[p][q] + c[i][j];
if (seen[p][q] == 0) {
push(s, (p, q));
seen[p][q] = 1;
}
}
}
}
The idea here is to always compute c[][] values for all positions having some given distance from the target before computing any c[][] value for a position having a larger distance, as the latter depend on the former.
The length of a shortest path will be d[1][1], and the number of such shortest paths will be c[1][1]. Total computation time is O(nx), which is clearly best-possible in an asymptotic sense.
My approach to this question would be backtracking as the number of squares in the x-axis and y-axis are different.
Note: Backtracking algorithms can be slow for certain cases and fast for the other
Create a 2-d Array for the chess-board. You know the staring index and the final index. To reach to the final index u need to keep close to the diagonal that's joining the two indexes.
From the starting index see all the indexes that the knight can travel to, choose the index which is closest to the diagonal indexes and keep on traversing, if there is no way to travel any further backtrack one step and move to the next location available from there.
PS : This is a bit similar to a well known problem Knight's Tour, in which choosing any starting point you have to find that path in which the knight whould cover all squares. I have codes this as a java gui application, I can send you the link if you want any help
Hope this helps!!
Try something. Draw boards of the following sizes: 1x1, 2x2, 3x3, 4x4, and a few odd ones like 2x4 and 3x4. Starting with the smallest board and working to the largest, start at the bottom left corner and write a 0, then find all moves from zero and write a 1, find all moves from 1 and write a 2, etc. Do this until there are no more possible moves.
After doing this for all 6 boards, you should have noticed a pattern: Some squares couldn't be moved to until you got a larger board, but once a square was "discovered" (ie could be reached), the number of minimum moves to that square was constant for all boards not smaller than the board on which it was first discovered. (Smaller means less than n OR less than x, not less than (n * x) )
This tells something powerful, anecdotally. All squares have a number associated with them that must be discovered. This number is a property of the square, NOT the board, and is NOT dependent on size/shape of the board. It is always true. However, if the square cannot be reached, then obviously the number is not applicable.
So you need to find the number of every square on a 200x200 board, and you need a way to see if a board is a subset of another board to determine if a square is reachable.
Remember, in these programming challenges, some questions that are really hard can be solved in O(1) time by using lookup tables. I'm not saying this one can, but keep that trick in mind. For this one, pre-calculating the 200x200 board numbers and saving them in an array could save a lot of time, whether it is done only once on first run or run before submission and then the results are hard coded in.
If the problem needs move sequences rather than number of moves, the idea is the same: save move sequences with the numbers.

Categories

Resources