My A* pathfinding algorithm does not give the shortest path - algorithm
I am writing an A* algorithm for my Godot game (Using GDScript), and something is messing up either when assigning node parentage or determining next best node that is causing the final path to take weird detours.
The function that calls the algorithm is below:
func mapClicked(pos):
if moveMode:
# Convert floats to ints.
pos = Vector3(int(pos[0]), int(pos[1]), int(pos[2]))
print("Moving to: " + str(pos))
var path = aStar(playerPos[0], playerPos[2], pos[0], pos[2])
print("\nPath: " + str(path))
Where the 'pos' variables represent a 3D coordinate in X, Y and Z.
The algorithm is shown below:
func aStar(x1, y1, x2, y2):
# Initialize open list with starting node.
var openList = [[x1, y1, 0]]
# Initialize closed list.
var closedList = []
# Initialize parents variable.
var parents = {}
while len(openList) > 0:
var q = null
for coord in openList:
if q == null or coord[2] < q[2]:
q = coord.duplicate()
openList.remove(openList.find(q))
# Generates successors
var successors = []
if q[0] - 1 >= 0:
successors.append([q[0] - 1, q[1]])
if q[0] + 1 < len(boardState):
successors.append([q[0] + 1, q[1]])
if q[1] - 1 >= 0:
successors.append([q[0], q[1] - 1])
if q[1] + 1 < len(boardState[q[0]]):
successors.append([q[0], q[1] + 1])
# Inspects successors.
for successor in successors:
# Calculates heuristic.
var g = distManhattan(x1, y1, successor[0], successor[1])
var h = distManhattan(x2, y2, successor[0], successor[1])
var f = g + h
# Sets successor's parent to the original node.
# TODO: FIX THIS.
if (not [q[0], q[1]] in parents) or (not (parents[[q[0], q[1]]][0] == successor[0] and parents[[q[0], q[1]]][1] == successor[1])) or (([[successor[0], successor[1]]] in parents) and (parents[[successor[0], successor[1]]][2] > f)):
parents[successor.duplicate()] = q.duplicate()
# Check for path found.
if successor[0] == x2 and successor[1] == y2:
# Generates path by moving backward through parentage.
print('\nOpen List: ' + str(openList))
print('\nClosed List: ' + str(closedList))
print('\nParentage:' + str(parents))
var path = [[successor[0], successor[1]]]
var currNode = parents[successor]
while currNode[0] != x1 or currNode[1] != y1:
path = [[currNode[0], currNode[1]]] + path
currNode = parents[[currNode[0], currNode[1]]]
path = [[currNode[0], currNode[1]]] + path
return path
# Determines whether current successor is better than the existing nodes.
var skip = false
for open in openList:
if open[0] == successor[0] and open[1] == successor[1]:
if open[2] <= f:
skip = true
break
if skip:
continue
for closed in closedList:
if closed[0] == successor[0] and closed[1] == successor[1]:
skip = true
break
if skip:
continue
successor.append(f)
openList.append(successor.duplicate())
closedList.append(q.duplicate())
And yes, I am aware that the z coordinates become y inside the algorithm, I just put it that way to make it easier for me to conceptualize.
And my print statements outputted the following:
I am at: (0, 0, 0)
map event triggered
Moving to: (5, 0, 4)
Open List: [[5, 0, 11], [5, 1, 11], [0, 6, 11], [5, 2, 11], [1, 6, 11], [5, 3, 11], [3, 5, 9], [2, 6, 11], [5, 4, 11]]
Closed List: [[0, 0, 0], [1, 0, 9], [0, 1, 9], [2, 0, 9], [1, 1, 9], [0, 2, 9], [3, 0, 9], [2, 1, 9], [1, 2, 9], [0, 3, 9], [4, 0, 9], [3, 1, 9], [2, 2, 9], [1, 3, 9], [0, 4, 9], [4, 1, 9], [3, 2, 9], [2, 3, 9], [1, 4, 9], [0, 5, 9], [4, 2, 9], [3, 3, 9], [2, 4, 9], [1, 5, 9], [4, 3, 9], [3, 4, 9], [2, 5, 9]]
Parentage:{[0, 1]:[0, 0, 0], [0, 2]:[0, 1, 9], [0, 3]:[0, 2, 9], [0, 4]:[0, 3, 9], [0, 5]:[0, 4, 9], [0, 6]:[0, 5, 9], [1, 0]:[1, 1, 9], [1, 1]:[1, 2, 9], [1, 2]:[1, 3, 9], [1, 3]:[1, 4, 9], [1, 4]:[1, 5, 9], [1, 5]:[0, 5, 9], [1, 6]:[1, 5, 9], [2, 0]:[2, 1, 9], [2, 1]:[2, 2, 9], [2, 2]:[2, 3, 9], [2, 3]:[2, 4, 9], [2, 4]:[2, 5, 9], [2, 5]:[1, 5, 9], [2, 6]:[2, 5, 9], [3, 0]:[3, 1, 9], [3, 1]:[3, 2, 9], [3, 2]:[3, 3, 9], [3, 3]:[3, 4, 9], [3, 4]:[2, 4, 9], [3, 5]:[2, 5, 9], [4, 0]:[4, 1, 9], [4, 1]:[4, 2, 9], [4, 2]:[4, 3, 9], [4, 3]:[4, 4, 9], [4, 4]:[3, 4, 9], [4, 5]:[4, 4, 9], [5, 0]:[4, 0, 9], [5, 1]:[4, 1, 9], [5, 2]:[4, 2, 9], [5, 3]:[4, 3, 9], [5, 4]:[4, 4, 9]}
Path: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 5], [2, 5], [2, 4], [3, 4], [4, 4], [4, 5]]
I tried the pseudocode given in geeksforgeeks' A* search algorithm post and that caused an infinite loop when tracing the parents to get the shortest path, due to two nodes being the parents of each other. Thus, I created conditionals to prevent those types of parents from being created. This fixed the infinite loop.
However, the path should be the shortest possible one, and my algorithm is taking the detour as shown.
Any and all help would be greatly appreciated.
Be aware that Godot has classes AStar and AStar2D that you could use. Similarly, you could make your implementation using Vector2 or Vector3.
I tried your code like this:
extends Spatial
var boardState =[
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""]
]
func _ready() -> void:
var playerPos = Vector3.ZERO
var pos = Vector3(5, 0, 4)
print("I am at: " + str(playerPos))
print("Moving to: " + str(pos))
var path = aStar(playerPos[0], playerPos[2], pos[0], pos[2])
print("\nPath: " + str(path))
func distManhattan(x1, y1, x2, y2):
return abs(x2 - x1) + abs(y2 - y1)
func aStar(x1, y1, x2, y2):
# Initialize open list with starting node.
var openList = [[x1, y1, 0]]
# Initialize closed list.
var closedList = []
# Initialize parents variable.
var parents = {}
while len(openList) > 0:
var q = null
for coord in openList:
if q == null or coord[2] < q[2]:
q = coord.duplicate()
openList.remove(openList.find(q))
# Generates successors
var successors = []
if q[0] - 1 >= 0:
successors.append([q[0] - 1, q[1]])
if q[0] + 1 < len(boardState):
successors.append([q[0] + 1, q[1]])
if q[1] - 1 >= 0:
successors.append([q[0], q[1] - 1])
if q[1] + 1 < len(boardState[q[0]]):
successors.append([q[0], q[1] + 1])
# Inspects successors.
for successor in successors:
# Calculates heuristic.
var g = distManhattan(x1, y1, successor[0], successor[1])
var h = distManhattan(x2, y2, successor[0], successor[1])
var f = g + h
# Sets successor's parent to the original node.
# TODO: FIX THIS.
if (not [q[0], q[1]] in parents) or (not (parents[[q[0], q[1]]][0] == successor[0] and parents[[q[0], q[1]]][1] == successor[1])) or (([[successor[0], successor[1]]] in parents) and (parents[[successor[0], successor[1]]][2] > f)):
parents[successor.duplicate()] = q.duplicate()
# Check for path found.
if successor[0] == x2 and successor[1] == y2:
# Generates path by moving backward through parentage.
print('\nOpen List: ' + str(openList))
print('\nClosed List: ' + str(closedList))
print('\nParentage:' + str(parents))
var path = [[successor[0], successor[1]]]
var currNode = parents[successor]
while currNode[0] != x1 or currNode[1] != y1:
path = [[currNode[0], currNode[1]]] + path
currNode = parents[[currNode[0], currNode[1]]]
path = [[currNode[0], currNode[1]]] + path
return path
# Determines whether current successor is better than the existing nodes.
var skip = false
for open in openList:
if open[0] == successor[0] and open[1] == successor[1]:
if open[2] <= f:
skip = true
break
if skip:
continue
for closed in closedList:
if closed[0] == successor[0] and closed[1] == successor[1]:
skip = true
break
if skip:
continue
successor.append(f)
openList.append(successor.duplicate())
closedList.append(q.duplicate())
This is the output I get:
I am at: (0, 0, 0)
Moving to: (5, 0, 4)
Open List: [[4, 4, 7], [3, 5, 7], [2, 6, 7], [1, 7, 7], [0, 8, 7], [9, 0, 9], [8, 1, 9], [7, 2, 9], [6, 3, 9]]
Closed List: [[0, 0, 0], [1, 0, -7], [0, 1, -7], [2, 0, -5], [1, 1, -5], [0, 2, -5], [3, 0, -3], [2, 1, -3], [1, 2, -3], [0, 3, -3], [4, 0, -1], [3, 1, -1], [2, 2, -1], [1, 3, -1], [0, 4, -1], [5, 0, 1], [4, 1, 1], [3, 2, 1], [2, 3, 1], [1, 4, 1], [0, 5, 1], [6, 0, 3], [5, 1, 3], [4, 2, 3], [3, 3, 3], [2, 4, 3], [1, 5, 3], [0, 6, 3], [7, 0, 5], [6, 1, 5], [5, 2, 5], [4, 3, 5], [3, 4, 5], [2, 5, 5], [1, 6, 5], [0, 7, 5], [8, 0, 7], [7, 1, 7], [6, 2, 7]]
Parentage:{[0, 1]:[0, 0, 0], [0, 2]:[0, 1, -7], [0, 3]:[0, 2, -5], [0, 4]:[0, 3, -3], [0, 5]:[0, 4, -1], [0, 6]:[0, 5, 1], [0, 7]:[0, 6, 3], [0, 8]:[0, 7, 5], [1, 0]:[1, 1, -5], [1, 1]:[1, 2, -3], [1, 2]:[1, 3, -1], [1, 3]:[1, 4, 1], [1, 4]:[1, 5, 3], [1, 5]:[1, 6, 5], [1, 6]:[0, 6, 3], [1, 7]:[0, 7, 5], [2, 0]:[2, 1, -3], [2, 1]:[2, 2, -1], [2, 2]:[2, 3, 1], [2, 3]:[2, 4, 3], [2, 4]:[2, 5, 5], [2, 5]:[1, 5, 3], [2, 6]:[1, 6, 5], [3, 0]:[3, 1, -1], [3, 1]:[3, 2, 1], [3, 2]:[3, 3, 3], [3, 3]:[3, 4, 5], [3, 4]:[2, 4, 3], [3, 5]:[2, 5, 5], [4, 0]:[4, 1, 1], [4, 1]:[4, 2, 3], [4, 2]:[4, 3, 5], [4, 3]:[3, 3, 3], [4, 4]:[3, 4, 5], [5, 0]:[5, 1, 3], [5, 1]:[5, 2, 5], [5, 2]:[5, 3, 7], [5, 3]:[4, 3, 5], [5, 4]:[5, 3, 7], [6, 0]:[6, 1, 5], [6, 1]:[6, 2, 7], [6, 2]:[5, 2, 5], [6, 3]:[5, 3, 7], [7, 0]:[7, 1, 7], [7, 1]:[6, 1, 5], [7, 2]:[6, 2, 7], [8, 0]:[7, 0, 5], [8, 1]:[7, 1, 7], [9, 0]:[8, 0, 7]}
Path: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [1, 6], [1, 5], [2, 5], [2, 4], [3, 4], [3, 3], [4, 3], [5, 3], [5, 4]]
Which is different from what you posted. Although it exhibits a similar behavior.
In the A* algorithm you check if you reached the goal before checking the successors. So the code you have under "Check for path found" would be for the current node, and be before the successors loop, instead of being for the current successor. Also, you can add the current node to the closed list early.
You should discard successors that are in the closed list early.
I also don't see you discarding not passable successors. I'll assume your particular graph is a full grid.
By the way, you are using elements [x, y, f], and you initialize the starting node with [x1, y1, 0] which might look odd because the value f for it would not be 0. It is not relevant for your particular graph.
These changes gives you a the correct behavior. I tried this code:
extends Spatial
var boardState =[
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", "", "", ""]
]
func _ready() -> void:
var playerPos = Vector3.ZERO
var pos = Vector3(5, 0, 4)
print("I am at: " + str(playerPos))
print("Moving to: " + str(pos))
var path = aStar(playerPos[0], playerPos[2], pos[0], pos[2])
print("\nPath: " + str(path))
func distManhattan(x1, y1, x2, y2):
return abs(x2 - x1) + abs(y2 - y1)
func aStar(x1, y1, x2, y2):
# Initialize open list with starting node.
var openList = [[x1, y1, distManhattan(x1, y1, x1, y2)]]
# Initialize closed list.
var closedList = []
# Initialize parents variable.
var parents = {}
while len(openList) > 0:
var q = null
for coord in openList:
if q == null or coord[2] < q[2]:
q = coord.duplicate()
openList.remove(openList.find(q))
# Check for path found.
if q[0] == x2 and q[1] == y2:
# Generates path by moving backward through parentage.
print('\nOpen List: ' + str(openList))
print('\nClosed List: ' + str(closedList))
print('\nParentage:' + str(parents))
var path = [[q[0], q[1]]]
var currNode = parents[[q[0], q[1]]]
while currNode[0] != x1 or currNode[1] != y1:
path = [[currNode[0], currNode[1]]] + path
currNode = parents[[currNode[0], currNode[1]]]
path = [[currNode[0], currNode[1]]] + path
return path
closedList.append(q.duplicate())
# Generates successors
var successors = []
if q[0] - 1 >= 0:
successors.append([q[0] - 1, q[1]])
if q[0] + 1 < len(boardState):
successors.append([q[0] + 1, q[1]])
if q[1] - 1 >= 0:
successors.append([q[0], q[1] - 1])
if q[1] + 1 < len(boardState[q[0]]):
successors.append([q[0], q[1] + 1])
# Inspects successors.
for successor in successors:
var skip = false
for closed in closedList:
if closed[0] == successor[0] and closed[1] == successor[1]:
skip = true
break
if skip:
continue
# Calculates heuristic.
var g = distManhattan(x1, y1, successor[0], successor[1])
var h = distManhattan(x2, y2, successor[0], successor[1])
var f = g + h
# Sets successor's parent to the original node.
# TODO: FIX THIS.
if (not [q[0], q[1]] in parents) or (not (parents[[q[0], q[1]]][0] == successor[0] and parents[[q[0], q[1]]][1] == successor[1])) or (([[successor[0], successor[1]]] in parents) and (parents[[successor[0], successor[1]]][2] > f)):
parents[successor.duplicate()] = q.duplicate()
# Determines whether current successor is better than the existing nodes.
for open in openList:
if open[0] == successor[0] and open[1] == successor[1]:
if open[2] <= f:
skip = true
break
if skip:
continue
successor.append(f)
openList.append(successor.duplicate())
And got this result:
I am at: (0, 0, 0)
Moving to: (5, 0, 4)
Open List: [[4, 5, 9], [3, 6, 9], [2, 7, 9], [1, 8, 9], [0, 9, 9], [9, 1, 11], [8, 2, 11], [7, 3, 11], [6, 4, 11]]
Closed List: [[0, 0, 0], [1, 0, -7], [0, 1, -7], [2, 0, -5], [1, 1, -5], [0, 2, -5], [3, 0, -3], [2, 1, -3], [1, 2, -3], [0, 3, -3], [4, 0, -1], [3, 1, -1], [2, 2, -1], [1, 3, -1], [0, 4, -1], [5, 0, 1], [4, 1, 1], [3, 2, 1], [2, 3, 1], [1, 4, 1], [0, 5, 1], [6, 0, 3], [5, 1, 3], [4, 2, 3], [3, 3, 3], [2, 4, 3], [1, 5, 3], [0, 6, 3], [7, 0, 5], [6, 1, 5], [5, 2, 5], [4, 3, 5], [3, 4, 5], [2, 5, 5], [1, 6, 5], [0, 7, 5], [8, 0, 7], [7, 1, 7], [6, 2, 7], [5, 3, 7], [4, 4, 7], [3, 5, 7], [2, 6, 7], [1, 7, 7], [0, 8, 7], [9, 0, 9], [8, 1, 9], [7, 2, 9], [6, 3, 9]]
Parentage:{[0, 1]:[0, 0, 0], [0, 2]:[0, 1, -7], [0, 3]:[0, 2, -5], [0, 4]:[0, 3, -3], [0, 5]:[0, 4, -1], [0, 6]:[0, 5, 1], [0, 7]:[0, 6, 3], [0, 8]:[0, 7, 5], [0, 9]:[0, 8, 7], [1, 0]:[0, 0, 0], [1, 1]:[0, 1, -7], [1, 2]:[0, 2, -5], [1, 3]:[0, 3, -3], [1, 4]:[0, 4, -1], [1, 5]:[0, 5, 1], [1, 6]:[0, 6, 3], [1, 7]:[0, 7, 5], [1, 8]:[0, 8, 7], [2, 0]:[1, 0, -7], [2, 1]:[1, 1, -5], [2, 2]:[1, 2, -3], [2, 3]:[1, 3, -1], [2, 4]:[1, 4, 1], [2, 5]:[1, 5, 3], [2, 6]:[1, 6, 5], [2, 7]:[1, 7, 7], [3, 0]:[2, 0, -5], [3, 1]:[2, 1, -3], [3, 2]:[2, 2, -1], [3, 3]:[2, 3, 1], [3, 4]:[2, 4, 3], [3, 5]:[2, 5, 5], [3, 6]:[2, 6, 7], [4, 0]:[3, 0, -3], [4, 1]:[3, 1, -1], [4, 2]:[3, 2, 1], [4, 3]:[3, 3, 3], [4, 4]:[3, 4, 5], [4, 5]:[3, 5, 7], [5, 0]:[4, 0, -1], [5, 1]:[4, 1, 1], [5, 2]:[4, 2, 3], [5, 3]:[4, 3, 5], [5, 4]:[4, 4, 7], [6, 0]:[5, 0, 1], [6, 1]:[5, 1, 3], [6, 2]:[5, 2, 5], [6, 3]:[5, 3, 7], [6, 4]:[6, 3, 9], [7, 0]:[6, 0, 3], [7, 1]:[6, 1, 5], [7, 2]:[6, 2, 7], [7, 3]:[6, 3, 9], [8, 0]:[7, 0, 5], [8, 1]:[7, 1, 7], [8, 2]:[7, 2, 9], [9, 0]:[8, 0, 7], [9, 1]:[8, 1, 9]}
Path: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [1, 4], [2, 4], [3, 4], [4, 4], [5, 4]]
Notice the resulting path does not overshoot or backpedal.
You have a commend "TODO: FIX THIS." so I'll have a closer look there. It looks like this:
# TODO: FIX THIS.
if (not [q[0], q[1]] in parents) or (not (parents[[q[0], q[1]]][0] == successor[0] and parents[[q[0], q[1]]][1] == successor[1])) or (([[successor[0], successor[1]]] in parents) and (parents[[successor[0], successor[1]]][2] > f)):
parents[successor.duplicate()] = q.duplicate()
You might express this differently. This is what you want:
The successor is not in the closed list (you already skipped those, so you might as well not check for that here).
The successor is not in the open list (we haven't seen it yet).
The successor is in the open list, and it would be better (checking f).
In fact, you will be checking the open list for the same criteria… And you would be skipping every node for which you should not update the parent. So just do this:
# Determines whether current successor is better than the existing nodes.
for open in openList:
if open[0] == successor[0] and open[1] == successor[1]:
if open[2] <= f:
skip = true
break
if skip:
continue
# Sets successor's parent to the original node.
parents[successor.duplicate()] = q.duplicate()
Everything else I can think of changing would be static typing or optimization. However, I will point a couple things:
Aside from the parents, you only ever modify arrays by appending. When extending the successor with f, you might as well create a new array directly and save the call to duplicate. Then you would only be modifying the open list, the closed list and the parents. Knowing this you would also know when you don't need to worry about duplicating the arrays.
You can initialize the current node with an INF on the third element, so it is never null.
Related
How do I generate a nested array that contains all possible 4 digit permutations 6 numbers with repeating values? ruby
Given a number of digits n = 1, 2, 3, 4, 5, 6. I wanted to generate a nested array S that will contain all possible 4 digit permutations of n. since 6^4 = 1296, there will be 1296 possible permutations. Example: S = [[1,1,1,1],[1,1,1,2],[1,1,2,2]...[6,6,6,6]] I started the nested loop with the first index with value of [1,1,1,1] Then used a for in loop with range 0..1295 and tried to carry over the value of S[i] to S[i+1] then increment the value of S[i+1][x], where x always starts at 3 then is decremented until it reaches 0 then it becomes 3 again. The problem with my procedure is when i try to increment the S[i+1][x], S[i] also increments its S[i][x]. In the code below S is instead called 'all_possible_combinations' all_possible_combinations = Array.new(1296) {Array.new(4)} all_possible_combinations[0] = [1, 1 ,1 ,1] x = 3 for i in 0..1295 if i + 1 == 1296 break else all_possible_combinations[i+1] = all_possible_combinations[i] all_possible_combinations[i+1][x] += 1 x -= 1 if x == 0 x = 3 end end end [Attached image shows debugging process where Si][x] also gets incremented
You may compute that array as follows. a = [1, 2, 3, 4, 5, 6] b = a.repeated_permutation(4).to_a #=> [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3], [1, 1, 1, 4], [1, 1, 1, 5], # [1, 1, 1, 6], [1, 1, 2, 1], [1, 1, 2, 2], [1, 1, 2, 3], [1, 1, 2, 4], # ... # [6, 6, 5, 3], [6, 6, 5, 4], [6, 6, 5, 5], [6, 6, 5, 6], [6, 6, 6, 1], # [6, 6, 6, 2], [6, 6, 6, 3], [6, 6, 6, 4], [6, 6, 6, 5], [6, 6, 6, 6]] b.size #=> 1296 See Array#repeated_permutation If the array a may contain duplicates and you wish to remove duplicate permutations you may wish to tack on Array#uniq. a = [1, 1, 3, 1, 1, 6] b = a.repeated_permutation(4).to_a.uniq #=> [[1, 1, 1, 1], [1, 1, 1, 3], [1, 1, 1, 6], [1, 1, 3, 1], # [1, 1, 3, 3], [1, 1, 3, 6], [1, 1, 6, 1], [1, 1, 6, 3], # [1, 1, 6, 6], [1, 3, 1, 1], [1, 3, 1, 3], [1, 3, 1, 6], # [1, 3, 3, 1], [1, 3, 3, 3], [1, 3, 3, 6], [1, 3, 6, 1], # [1, 3, 6, 3], [1, 3, 6, 6], [1, 6, 1, 1], [1, 6, 1, 3], # [1, 6, 1, 6], [1, 6, 3, 1], [1, 6, 3, 3], [1, 6, 3, 6], # [1, 6, 6, 1], [1, 6, 6, 3], [1, 6, 6, 6], [3, 1, 1, 1], # [3, 1, 1, 3], [3, 1, 1, 6], [3, 1, 3, 1], [3, 1, 3, 3], # [3, 1, 3, 6], [3, 1, 6, 1], [3, 1, 6, 3], [3, 1, 6, 6], # [3, 3, 1, 1], [3, 3, 1, 3], [3, 3, 1, 6], [3, 3, 3, 1], # [3, 3, 3, 3], [3, 3, 3, 6], [3, 3, 6, 1], [3, 3, 6, 3], # [3, 3, 6, 6], [3, 6, 1, 1], [3, 6, 1, 3], [3, 6, 1, 6], # [3, 6, 3, 1], [3, 6, 3, 3], [3, 6, 3, 6], [3, 6, 6, 1], # [3, 6, 6, 3], [3, 6, 6, 6], [6, 1, 1, 1], [6, 1, 1, 3], # [6, 1, 1, 6], [6, 1, 3, 1], [6, 1, 3, 3], [6, 1, 3, 6], # [6, 1, 6, 1], [6, 1, 6, 3], [6, 1, 6, 6], [6, 3, 1, 1], # [6, 3, 1, 3], [6, 3, 1, 6], [6, 3, 3, 1], [6, 3, 3, 3], # [6, 3, 3, 6], [6, 3, 6, 1], [6, 3, 6, 3], [6, 3, 6, 6], # [6, 6, 1, 1], [6, 6, 1, 3], [6, 6, 1, 6], [6, 6, 3, 1], # [6, 6, 3, 3], [6, 6, 3, 6], [6, 6, 6, 1], [6, 6, 6, 3], # [6, 6, 6, 6]] b.size #=> 81
To create a sequence where each element is generated based on the previous one, there's Enumerator.produce, e.g.: enum = Enumerator.produce([1, 1, 1, 1]) do |a, b, c, d| d += 1 # ^^^^^^^^^^^^ # initial value if d > 6 d = 1 c += 1 end if c > 6 c = 1 b += 1 end if b > 6 b = 1 a += 1 end if a > 6 raise StopIteration # <- stops enumeration end [a, b, c, d] # <- return value = next value end I've kept the example intentionally simple, using an explicit variable for each of the four digits. You could of course also have an array and use a little loop to handle the increment / carry. The above gives you: enum.count #=> 1296 enum.first(3) #=> [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3]] enum.to_a.last(3) #=> [[6, 6, 6, 4], [6, 6, 6, 5], [6, 6, 6, 6]]
Yen's K shortest Path giving incorrect results (Python)
I am trying to implement the Yen's K Shortest Path Algorihtm based on the pseudo-code from https://en.wikipedia.org/wiki/Yen%27s_algorithm. Here is the code. import numpy as np import networkx as nx edge_list = [[0, 1], [0, 2], [0, 7], [1, 2], [1, 9], [2, 5], [2, 7], [2, 9], [3, 4], [3, 5], [3, 6], [3, 8], [4, 5], [4, 6], [4, 7], [4, 8], [5, 6], [5, 7], [5, 8], [6, 8], [7, 8]] graph = nx.Graph() graph.add_edges_from(edge_list) nx.draw(graph, with_labels = True) source_node = 8 destination_node = 9 def yen_ksp(graph, source, sink, K): A, B = [], [] A.append(nx.shortest_path(graph, source=source, target=sink)) for k in range(1, 1+K): for i in range(len(A[k - 1]) - 1): spurNode = A[k-1][i] rootPath = A[k-1][0:i+1] removed_edges, removed_nodes = [], [] for p in A: if rootPath == p[0:i+1] and p[i:i+2] not in removed_edges: removed_edges.append(p[i:i+2]) for edge in removed_edges: graph.remove_edge(edge[0], edge[1]) try: spurPath = nx.shortest_path(graph, source=spurNode, target=sink) except: for edge in removed_edges: graph.add_edge(edge[0], edge[1]) continue totalPath = rootPath + spurPath[1:] B.append(totalPath) for edge in removed_edges: graph.add_edge(edge[0], edge[1]) if B == []: # This handles the case of there being no spur paths, or no spur paths left. # This could happen if the spur paths have already been exhausted (added to A), # or there are no spur paths at all - such as when both the source and sink vertices # lie along a "dead end". break B.sort() A.append(B[-1]) B.pop(-1) return A print(yen_ksp(graph.copy(), source_node, destination_node, 10)) This is supposed to be an undirected, unweighted graph generated from the above code. And this is the output of the code. [[8, 5, 2, 9], [8, 7, 2, 9], [8, 7, 2, 1, 9], [8, 7, 2, 1, 2, 9], [8, 7, 2, 1, 2, 1, 9], [8, 7, 2, 1, 2, 1, 2, 9], [8, 7, 2, 1, 2, 1, 2, 1, 9], [8, 7, 2, 1, 2, 1, 2, 1, 2, 9], [8, 7, 2, 1, 2, 1, 2, 1, 2, 1, 9], [8, 7, 2, 1, 2, 1, 2, 1, 2, 1, 2, 9], [8, 7, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 9]] Obviously there are shorter paths that the algorithm missed. And, the results contain paths that have loops. I want only the ones without. Also, in other cases, the results were in the wrong order, some longer paths appear before other paths that are shorter. In the KSP problem, the order of results is obviously important because if I stop at some k, I want to be sure that there is no shorter path that I have missed. I am open to other algorithms that can correctly and effectively solve this problem of KSP without loops on undirected-unweighted graphs. Please help.
Networkx provides a function for generating a list of all simple paths in a graph from source to target, starting from shortest ones: shortest_simple_paths. This procedure is based exactly on Yen's algorithm, as you can read in the documentation. Using it is very simple: paths = list(nx.shortest_simple_paths(graph, source_node, target_node)) If you want only the first K shortest paths you can make use of islice: from itertools import islice paths = list(islice(nx.shortest_simple_paths(graph, source_node, target_node), K)) Example: from itertools import islice K = 10 source_node = 8 target_node = 9 graph = nx.Graph() edge_list = [[0, 1], [0, 2], [0, 7], [1, 2], [1, 9], [2, 5], [2, 7], [2, 9], [3, 4], [3, 5], [3, 6], [3, 8], [4, 5], [4, 6], [4, 7], [4, 8], [5, 6], [5, 7], [5, 8], [6, 8], [7, 8]] graph.add_edges_from(edge_list) for path in islice(nx.shortest_simple_paths(graph, source_node, target_node), K): print(path) Output: [8, 5, 2, 9] [8, 7, 2, 9] [8, 5, 7, 2, 9] [8, 5, 2, 1, 9] [8, 3, 5, 2, 9] [8, 7, 0, 1, 9] [8, 7, 2, 1, 9] [8, 4, 5, 2, 9] [8, 7, 5, 2, 9] [8, 7, 0, 2, 9] If you want to understand how shortest_simple_path is implemented you can check out its source code: it's well written and very easy to understand!
Breaking a matrix into smaller sub-lists
I can't wrap my head around how to achieve this. To be more specific, I would like to break the following matrix matrix = [[7, 9, 1, 8, 9, 1], [4, 2, 1, 2, 1, 5], [3, 2, 3, 1, 2, 3], [7, 9, 11, 6, 4, 8], [8, 9, 22, 3, 1, 9], [1, 1, 1, 1, 1, 1]] into: [[7, 9, 4, 2], [1, 8, 1, 2], [9, 1, 1, 5], [3, 2, 7, 9], [3, 1, 11, 6], [2, 3, 4, 8], [8, 9, 1, 1], [22, 3, 1, 1], [1, 9, 1, 1]] Or equivalently, [[7, 9, 4, 2], [1, 8, 1, 2], [9, 1, 1, 5], [3, 2, 7, 9], [3, 1, 11, 6], [2, 3, 4, 8], [8, 9, 1, 1], [22, 3, 1, 1], [1, 9, 1, 1]] Here is what I have tried doing: def split([[]]) -> [[]] split_matrix = [] temp_map = [] row_limit, col_limit = 2, 2 for row in range(len(elevation_map)): for col in range(len(elevation_map)): elevation = elevation_map[row][col] if row < row_limit and col < col_limit: temp_map.append(elevation) split_matrix.append(temp_map) return split_matrix However, I had no luck in doing so. Is there a way to do it without using libraries like numpy? Is it possible?
The solution is going to be neater if we write a helper function to extract one 2x2 sub-matrix into a list. After that, it's a simple list comprehension, iterating over the coordinates of the top-left of each submatrix. def split_matrix(matrix, rows=2, cols=2): def helper(i, j): out = [] for row in matrix[i:i+rows]: out.extend(row[j:j+cols]) return out width, height = len(matrix[0]), len(matrix) return [ helper(i, j) for i in range(0, height, rows) for j in range(0, width, cols) ]
Ruby, merging lazy sequences
Let i have lazy sequences: s1, s2, s3, ..., sN, with non-descending numbers, for example: s1 = [1, 1, 2, 3, 3, 3, 4, .....] s2 = [1, 2, 2, 2, 2, 2, 3, 3, 4, ....] s3 = [1, 2, 3, 3, 3, 3, 4, 4, 4, ....] what I'd like to do - is to merge it, grouping by similar items and processing it with some function, for example generate list of tuples (number, count) for my case: merge(s1, s2, s3) should generate [ [1, 4], [2, 6], [3, 9], [4, 5], .... ] Are any gems, etc., to process such sequences
If you want to do it lazily, here is some code which would do that: def merge(*args) args.map!(&:lazy) Enumerator.new do |yielder| while num = args.map(&:peek).min count = 0 while list = args.find { |l| l.peek == num } list.next list.peek rescue args.delete list count += 1 end yielder.yield [num, count] end end end s1 = [1, 1, 2, 3, 3, 3, 4] s2 = [1, 2, 2, 2, 2, 2, 3, 3, 4] s3 = [1, 2, 3, 3, 3, 3, 4, 4, 4] s4 = (0..1.0/0) merge(s1, s2, s3, s4).take(20) # => [[0, 1], [1, 5], [2, 8], [3, 10], [4, 6], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1], [10, 1], [11, 1], [12, 1], [13, 1], [14, 1], [15, 1], [16, 1], [17, 1], [18, 1], [19, 1]]
Looking for more elegant solutions
I'm new to ruby and working on such a problem: There are n numbered letters and n numbered envelopes. The letter x can't be put into the envelope x.(OP only wants values where no value of x is at index x-1) What I want is to print out all the possible cases. The index of Array + 1 ---> the number of the envelop The element of Array ---> the number of the letter Input: n = 3. Output: [2, 3, 1], [3, 1, 2] Input: n = 4. Output: [2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1] Here is my code: $nums = [] def f( already, n, times ) if n > times $nums << already.dup return else 1.upto(times) do |i| next if ((already.include? i) || n == i) already << i f( already, n+1, times ) already.pop end end end I'm looking for more elegant solutions.
Make use of the permutation enumerator, rejecting all those where the value at index x-1 matches x: def f(n, x) (1..n).to_a.permutation.reject{|p| p[x-1] == x} end > f 3, 3 => [[1, 3, 2], [2, 3, 1], [3, 1, 2], [3, 2, 1]] > f 4, 4 => [[1, 2, 4, 3], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [3, 1, 4, 2], [3, 2, 4, 1], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 1, 3, 2], [4, 2, 1, 3], [4, 2, 3, 1], [4, 3, 1, 2], [4, 3, 2, 1]] UPDATE Looking at your question again, it's unclear if you want to use a specific x, or just that the logic should hold true for any value of x. If the second guess is what you want, then use this instead: def f(n) (1..n).to_a.permutation.reject{|p| p.any?{|x| p[x-1] == x}} end > f 3 => [[2, 3, 1], [3, 1, 2]] > f 4 => [[2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1]]