Yen's Algorithm implementation not choosing shortest paths - python
I am using Yen's Algorithm (Wikipedia) to find k shortest paths in a graph. In the example below, my graph is a dictionary where each node is a key, with its value being the neighbors. Map() from dotmap simply allows for dictionaries to be converted into an object where keys can be accessed with dot notation. I want to find the four shortest paths in descending order from A to F where every edge has equal weight. The first two are ties (A > B > D > F) and (A > E > D > F), and the next two are (A > B > C > G > F) and finally (A > B > D > C > G > F). It is possible that my implementation of Dijkstra's (called AStar despite having no heuristic) is flawed because it is returning an empty list when no path is found. How can I have my code only pick the valid paths? Currently it returns [['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], [], []] -- it should return [['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], ['A', 'B', 'C', 'G', 'F'], ['A', 'B', 'D', 'C', 'G', 'F']] which are the shortest paths.
import copy
import heapq
from dotmap import Map
from itertools import count
graph = {
'A': ['B', 'E'],
'B': ['C', 'D'],
'C': ['G'],
'D': ['C', 'F'],
'E': ['D'],
'F': [],
'G': ['F']
}
class PriorityQueue:
def __init__(self):
self.elements = []
self._counter = count()
def empty(self):
return len(self.elements) == 0
def put(self, item, priority):
heapq.heappush(self.elements, (priority, item,))
def get(self):
return heapq.heappop(self.elements)[1]
class AStar:
def __init__(self, graph, start, goals=[]):
self.graph = graph
self.start = start
self.frontier = PriorityQueue()
self.frontier.put(start, 0)
self.previous = {}
self.previous[start] = None
self.costs = {}
self.costs[start] = 0
self.final_path = None
self.goals = goals
self.goal = None
def search(self):
graph = self.graph
frontier = self.frontier
goals = self.goals
costs = self.costs
while not frontier.empty():
state = frontier.get()
if state in goals:
cost = self.costs[state]
self.goal = state
self.final_path = self.trace_path()
return Map({'path': self.final_path, 'cost': cost})
for next_state in graph[state]:
new_cost = costs[state] + 1
if next_state not in costs or new_cost < costs[next_state]:
costs[next_state] = new_cost
priority = new_cost
frontier.put(next_state, priority)
self.previous[next_state] = state
# No path found
return Map({'path': [], 'cost': 0})
def trace_path(self):
current = self.goal
path = []
while current != self.start:
path.append(current)
current = self.previous[current]
path.append(self.start)
path.reverse()
return path
def YenKSP(graph, source, sink, k_paths):
graph_clone = copy.deepcopy(graph)
A = [AStar(graph, source, sink).search().path]
B = []
for k in range(1, k_paths):
for i in range(len(A[-1]) - 1):
spur_node = A[-1][i]
root_path = A[-1][:i+1]
for path in A:
if len(path) > i and root_path == path[:i+1]:
graph_clone[path[i]].remove(path[i+1])
result = AStar(graph_clone, spur_node, sink).search()
spur_path = result.path
total_path = root_path[:-1] + spur_path
spur_cost = AStar(graph_clone, source, spur_node).search().cost
B.append(Map({'path': total_path, 'cost': result.cost + spur_cost}))
graph_clone = copy.deepcopy(graph)
if len(B) == 0:
break
B.sort(key=lambda p: (p.cost, len(p.path)))
A.append(B[0].path)
B.pop()
return A
paths = YenKSP(graph, 'A', 'F', 4)
print(paths)
import copy
import heapq
#from dotmap import Map
from itertools import count
class Map(dict):
def __getattr__(self, k):
return self[k]
def __setattr__(self, k, v):
self[k] = v
graph = {
'A': ['B', 'E'],
'B': ['C', 'D'],
'C': ['G'],
'D': ['C', 'F'],
'E': ['D'],
'F': [],
'G': ['F']
}
class PriorityQueue:
def __init__(self):
self.elements = []
self._counter = count()
def empty(self):
return len(self.elements) == 0
def put(self, item, priority):
heapq.heappush(self.elements, (priority, item,))
def get(self):
return heapq.heappop(self.elements)[1]
class AStar:
def __init__(self, graph, start, goals=[]):
self.graph = graph
self.start = start
self.frontier = PriorityQueue()
self.frontier.put(start, 0)
self.previous = {}
self.previous[start] = None
self.costs = {}
self.costs[start] = 0
self.final_path = None
self.goals = goals
self.goal = None
def search(self):
graph = self.graph
frontier = self.frontier
goals = self.goals
costs = self.costs
while not frontier.empty():
state = frontier.get()
if state in goals:
cost = self.costs[state]
self.goal = state
self.final_path = self.trace_path()
return Map({'path': self.final_path, 'cost': cost})
for next_state in graph[state]:
new_cost = costs[state] + 1
if next_state not in costs or new_cost < costs[next_state]:
costs[next_state] = new_cost
priority = new_cost
frontier.put(next_state, priority)
self.previous[next_state] = state
# No path found
return Map({'path': [], 'cost': float('inf')})
def trace_path(self):
current = self.goal
path = []
while current != self.start:
path.append(current)
current = self.previous[current]
path.append(self.start)
path.reverse()
return path
def YenKSP(graph, source, sink, k_paths):
A = [AStar(graph, source, sink).search().path]
B = []
for _ in range(1, k_paths):
for i in range(len(A[-1]) - 1):
graph_clone = copy.deepcopy(graph)
spur_node = A[-1][i]
root_path = A[-1][:i+1]
for path in A:
if len(path) > i and root_path == path[:i+1]:
if path[i+1] in graph_clone[path[i]]:
graph_clone[path[i]].remove(path[i+1])
result = AStar(graph_clone, spur_node, sink).search()
spur_path = result.path
total_path = root_path[:-1] + spur_path
spur_cost = AStar(graph_clone, source, spur_node).search().cost
B.append(Map({'path': total_path, 'cost': result.cost + spur_cost}))
if len(B) == 0:
break
B.sort(key=lambda p: (p.cost, len(p.path)))
best_b = B.pop(0)
if best_b.cost != float('inf'):
A.append(best_b.path)
return A
paths = YenKSP(graph, 'A', 'F', 4)
print(paths)
Produces:
[['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], ['A', 'B', 'C', 'G', 'F'], ['A', 'B', 'D', 'C', 'G', 'F']]
The main issue was that when there was no path found, your default returned a path with 0 cost. So when sorted by path cost, these paths were appearing as the best choice in B and being added to A. I changed the default path cost to float('inf'). Doing so revealed an error that could occur when you tried to remove the same edge twice from graph_clone (inside for path in A: ...), so I added an if check to conditionally remove the edge. The two last things the diff indicate that I did were (a) imitate your dotmap.Map class (you can remove this and uncomment the import), and (b) only add a path to the resultset A if the cost is finite.
Related
A more elegant way to return a list of node values from depth-first recursive function in Python 3?
I'm working out how to return a list of values from a depth-first traversal of a binary tree. Specifically, I'm trying to do so with a recursive function. After playing around a bit I got this code to properly return the list of expected values, but using a try/finally block in order to pull it off seems clunky to me. def depth_first_recursive(root, node_list: list): try: if not root: return else: node_list.append(root.val) depth_first_recursive(root.left, node_list) depth_first_recursive(root.right, node_list) finally: return node_list class Node: def __init__(self, val): self.val = val self.left = None self.right = None values = [] if __name__ == '__main__': a = Node('a') b = Node('b') c = Node('c') d = Node('d') e = Node('e') f = Node('f') a.left = b a.right = c b.left = d b.right = e c.right = f nodelist = [] print(depth_first_recursive(a, node_list=nodelist)) returns ['a', 'b', 'd', 'e', 'c', 'f', ] Is there a better way to pull this off in Python?
Simplified yours, much more efficient than all the copying of the others. Yours takes O(n) time, theirs is O(n²). def depth_first_recursive(root, node_list: list): if root: node_list.append(root.val) depth_first_recursive(root.left, node_list) depth_first_recursive(root.right, node_list) return node_list
I have applied the same logic as yours but tried to make code compact: def depth_first_recursive(root, node_list: list): if root is None: return [] return [root.val]+depth_first_recursive(root.left, node_list) + depth_first_recursive(root.right, node_list) Output: ['a', 'b', 'd', 'e', 'c', 'f']
Don't need to maintain a node_list, just have more faith in the structure of your recursion. def depth_first_recursive(root): if root is None: return [] # Terminal case when reaching bottom of tree elif not root.left and not root.right: return [root.val] # Get current value and continue to append list else: return [root.val] + depth_first_recursive(root.left) + depth_first_recursive(root.right)
I need a fix for the structure of my binary tree (python)
Hi i am not getting the output i want. The inorder traversal is supposed to yield: MAFXUEN The preorder traversel is supposed to yield: EXAMFUN class Node: def init(self, data): self.left = None self.right = None self.data = data # Insert Node def insert(self, data): if self.data: if data < self.data: if self.left is None: self.left = Node(data) else: self.left.insert(data) elif data > self.data: if self.right is None: self.right = Node(data) else: self.right.insert(data) else: self.data = data # Print the Tree def PrintTree(self): if self.left: self.left.PrintTree() print( self.data), if self.right: self.right.PrintTree() # Inorder traversal # Left -> Root -> Right def inorderTraversal(self, root): res = [] if root: res = self.inorderTraversal(root.left) res.append(root.data) res = res + self.inorderTraversal(root.right) return res def PreorderTraversal(self, root): res = [] if root: res.append(root.data) res = res + self.PreorderTraversal(root.left) res = res + self.PreorderTraversal(root.right) return res root = Node('E') root.insert('X') root.insert('A') root.insert('M') root.insert('F') root.insert('U') root.insert('N') print('In-Order:\t', root.inorderTraversal(root)) print('Pre-Order:\t', root.PreorderTraversal(root)) Instead the output is In-Order: ['A', 'E', 'F', 'M', 'N', 'U', 'X'] Pre-Order: ['E', 'A', 'X', 'M', 'F', 'U', 'N'] Process finished with exit code 0 I am fairly new to python so I have limited knowledge in what i can do to resolve this. Luckily I am a visual learner so if anyone has a fix i can study it and use it in the future!
Python DFS nested dictionary
I've written a function which should be able to search a nested dictionary, using DFS, to find a specific value. The recursive element seems to be working fine, however, when the base case should return True, it simply doesn't. obj = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'i':'j'}]} def obj_dfs(obj, target): if type(obj) == dict: for key, val in obj.items(): obj_dfs(key, target) obj_dfs(val, target) elif type(obj) in (list, tuple): for elem in obj: obj_dfs(elem, target) else: if obj == target: print(f"{obj} == {target}") return True else: print(f"{obj} != {target}") return False obj_dfs(obj, 'j') And the results. As you can see, the standard output "i==i" shows that this element was evaluated correctly but the return True statement isn't functioning as intended. I've verified that if I call obj_dfs(obj, 'j'), that experiences the same error. a != j c != j d != j e != j f != j b != j g != j h != j i != j j == j False Why is this? And how can I fix this?
As the comments point out, you need to return the results of the recursive calls. Since you are just looking for a True/False match, you can pass the recursive calls into any() which will exit early with True if there is a match. The base case can simple be whether obj == target. obj = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'i':'j'}]} def obj_dfs(obj, target): if obj == target: return True if isinstance(obj, dict): return any(obj_dfs(v, target) for v in obj.items()) elif isinstance(obj, (list, tuple)): return any(obj_dfs(l, target) for l in obj) return False obj_dfs(obj, 'i'), obj_dfs(obj, 'j'), obj_dfs(obj, 'd'), obj_dfs(obj, 'x') # (True, True, True, False) This allows for a three simple blocks. Notice we are checking for a tuple as well as a list in the last isinstance. This allows you to simply pass in the dict item()s rather than looping over keys and values independently. Adding a print(obj) as the first line of the function will show the order in which you are traversing the data. For example obj_dfs(obj, 'j') will print: {'a': [{'c': 'd'}, {'e': 'f'}], 'b': [{'g': 'h'}, {'i': 'j'}]} ('a', [{'c': 'd'}, {'e': 'f'}]) a [{'c': 'd'}, {'e': 'f'}] {'c': 'd'} ('c', 'd') c d {'e': 'f'} ('e', 'f') e f ('b', [{'g': 'h'}, {'i': 'j'}]) b [{'g': 'h'}, {'i': 'j'}] {'g': 'h'} ('g', 'h') g h {'i': 'j'} ('i', 'j') i j
I made some edits to your code obj = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'i':'j'}]} def obj_dfs(obj, target): if type(obj) == dict: for key, val in obj.items(): if(key==target): return val else: result=obj_dfs(val, target) if result!=None: return result elif type(obj) in (list, tuple): for elem in obj: result=obj_dfs(elem, target) if result!=None: return result else: if obj==target: return True print(obj_dfs(obj, 'i')) I don't know why you would just return true instead of the value though, so I put it that if its a dictionary key it would return the value, instead it would return true, to show that it is found
Expanding on my comment, try this, where we pass return values up the chain and always return True if a child returned True: obj = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'i':'j'}]} obj2 = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'g':'j'}]} def obj_dfs(obj, target): if type(obj) == dict: for key, val in obj.items(): keyret = obj_dfs(key, target) valueret = obj_dfs(val, target) if keyret is True or valueret is True: return True else: return False elif type(obj) in (list, tuple): rets = [] for elem in obj: rets.append(obj_dfs(elem, target)) if True in rets: return True else: return False else: if obj == target: print(f"{obj} == {target}") return True else: print(f"{obj} != {target}") return False print(obj_dfs(obj, 'i')) print(obj_dfs(obj2, 'i'))
Recursion is a functional heritage and so using it with functional style yields the best results. This means decoupling concerns and pushing side effects to the fringe of your program. obj_dfs performs a depth-first traversal and mixes in search logic. And for purposes of debugging, includes a print side effect. Decomposition results in functions that are easier to write, test, and reuse in various parts of our program. We'll start with a generic dfs for traversal - def dfs(t, path = []): if isinstance(t, dict): for key, val in t.items(): yield from dfs(val, [*path, key]) elif isinstance(t, (list, tuple)): for key, val in enumerate(t): yield from dfs(val, [*path, key]) else: yield path, t obj = {'a': [{'c':'d'}, {'e':'f'}], 'b': [{'g':'h'}, {'i':'j'}]} for path, val in dfs(obj): print(path, val) # side effect decided by caller ['a', 0, 'c'] d ['a', 1, 'e'] f ['b', 0, 'g'] h ['b', 1, 'i'] j The suggested solution and other answers here collapse the semantic difference between key and value, providing no differentiation on the particular match of target. Writing dfs as we did above, we can know which part of obj matched. keys values ['a', 0, 'c'] d ['a', 1, 'e'] f ['b', 0, 'g'] h ['b', 1, 'i'] j has_value and has_key are easily defined in terms of dfs - def has_value(t, target): for path, val in dfs(t): if val == target: return True return False def has_key(t, target): for path, val in dfs(t): if target in path: return True return False print(has_value(obj, "j")) # True print(has_key(obj, "j")) # False print(has_value(obj, "i")) # False print(has_key(obj, "i")) # True
Can't stop python from iterating through string in loop
class Hat: def __init__(self, **kwargs): self.contents = [] for balltype in kwargs.keys(): for ballnum in range(kwargs[balltype]): self.contents += balltype hattrial = Hat(red = 1, blue = 2) print(hattrial.contents) I'm trying top create a list that contains the keys from the input argument dictionary, but instead of simply adding the string entry I get: ['r', 'e', 'd', 'b', 'l', 'u', 'e', 'b', 'l', 'u', 'e'] Instead of: ['red', 'blue', 'blue'] Where red occurs once and blue occurs twice. I've tried a few different solutions short of just manipulating the array afterwards such as the attempt below but nothing I've done has changed the output. Surely there's an elegant solution that doesn't require me sticking characters back together? end = len(balltype) self.contents += balltype[0:end] self.contents += balltype
Using append class Hat: def __init__(self, **kwargs): self.contents = [] for balltype in kwargs.keys(): for ballnum in range(kwargs[balltype]): self.contents.append(balltype) hattrial = Hat(red = 1, blue = 2) print(hattrial.contents) Be careful with the += operator in lists This also works, try to understand why it appends correctly here with += class Hat: def __init__(self, **kwargs): self.contents = [] for balltype in kwargs.keys(): self.contents += kwargs[balltype] * [balltype] hattrial = Hat(red = 1, blue = 2) print(hattrial.contents) Basically, the problem with your code can be reduced to the following: a = [] a += "hello" a ['h', 'e', 'l', 'l', 'o']
Using tuple as key for nested properties with json.dump
Not sure how to use a tuple as a set of strings the way I would like. I would like my json to look like: 'item': { 'a': { 'b': { 'c': 'somevalue' } } } Which could be done with: item = {} item['a']['b']['c'] = "somevalue" However a, b, and c are dynamic, so I understand I need to use a tuple, but this does not do what I would like: item = {} path = ('a','b','c') item[path] = "somevalue" json.dump(item, sys.stdout) So I am getting the error: TypeError("key " + repr(key) + " is not a string" How do I dynamically get item['a']['b']['c']?
AFAIK there are no builtins for this task, so you need to write a couple of recursive functions: def xset(dct, path, val): if len(path) == 1: dct[path[0]] = val else: if path[0] not in dct: dct[path[0]] = {} xset(dct[path[0]], path[1:], val) def xget(dct, path): if len(path) == 1: return dct[path[0]] else: return xget(dct[path[0]], path[1:]) Usage: >>> d = {} >>> xset(d, ('a', 'b', 'c'), 6) >>> d {'a': {'b': {'c': 6}}} >>> xset(d, ('a', 'b', 'd', 'e'), 12) >>> d {'a': {'b': {'c': 6, 'd': {'e': 12}}}} >>> xget(d, ('a', 'b', 'c')) 6
Try this: item = {} for i in reversed(path): tmp = {**item} item ={} item[i] = {**tmp} if path.index(i)!=len(path)-1 else 'somevalue'