Python: List all possible paths in graph represented by dictionary - algorithm

I have a dictionary with keys representing nodes and values representing possible nodes that the key can traverse to.
Example:
dependecyDict = { 'A': ['D'], 'B': ['A', 'E'], 'C': ['B'], 'D': ['C'], 'G':['H']}
I want to create a new dicitonary, ChainsDict, that will contain all 'values' that each 'key' can traverse to by means of dependecyDict.
For example, the output of the program with this example will be:
ChainsDict = {'A': ['D', 'C', 'B','E'], 'B':['A','D','C','E'], 'C':['B','A','D','E'], 'D':['C','B','A','E'], 'G': ['H']}
I think using a recursive algorithm is the best way to go about making a solution and I tried modifying a shortest path traversing algorithm as follows:
def helper(dependencyDict, ChainsDict):path = []
for key in dependencyDict:
path = path + [(recursiveRowGen(dependencyDict,key))]
for paths in path:
ChainsDict[paths[0]] = paths[1:]
print(finalLineDict)
def recursiveRowGen(dependencyDict,key,path = []):
path = path + [key]
if not key in dependencyDict:
print("no key: ",key)
return path
print(dependencyDict[key])
for blocking in dependencyDict[key]:
if blocking not in path:
newpath = recursiveRowGen(dependencyDict,blocking,path)
if newpath:
return newpath
return path
This code however is having problems capturing the correct output when a key in dependecyDict has more than one value.
I found a hacky solution but it doesn't feel very elegant. Any help is appreciated, thanks!

Here is a recursive solution:
Code
def get_chain_d(argDict):
def each_path(i,caller_chain):
a=[]
caller_chain.append(i)
b = argDict.get(i,[])
for j in b:
if j not in caller_chain:
a.append(j)
a.extend(each_path(j,caller_chain))
return a
return {i:each_path(i,[]) for i in argDict}
dependecyDict = { 'A': ['D'], 'B': ['A', 'E'], 'C': ['B'], 'D': ['C'], 'G':['H']}
print(get_chain_d(dependecyDict))
Output:
{'B': ['A', 'D', 'C', 'E'], 'A': ['D', 'C', 'B', 'E'], 'D': ['C', 'B', 'A', 'E'], 'C': ['B', 'A', 'D', 'E'], 'G': ['H']}

This is basically a graph traversal problem. You can represent each of your key as a node in a graph and its values are the nodes it is connected to.
You can do either Depth-first-search or breadth-first-search for graph. Of course, there's also an iterative and a recursive solution for each of these method. Here's an iterative implementation (I added a few conditionals to eliminate loops):
dependencyDict = { 'A': ['D'], 'B': ['A', 'E'], 'C': ['B'], 'D': ['C'], 'G':['H'] }
chainsDict = {}
for key in dependencyDict:
currKey = key
frontier = [key]
visited = []
while frontier:
currKey = frontier[0]
frontier.remove(currKey)
if dependencyDict.get(currKey,0) and (currKey not in visited) and (currKey not in frontier):
nodes = dependencyDict[currKey]
frontier.extend(nodes)
visited.append(currKey)
elif currKey in visited:
visited.remove(currKey)
elif dependencyDict.get(currKey,0) == 0:
visited.append(currKey)
for i in visited:
if i == key:
visited.remove(i)
chainsDict[key] = visited
print chainsDict
The result looks like:
{'A': ['D', 'C', 'B', 'E'], 'C': ['B', 'A', 'E', 'D'], 'B': ['A', 'E', 'D', 'C'], 'D': ['C', 'B', 'A', 'E'], 'G': ['H']}

Related

Permutation of arrays

There are multiple sets of elements like:
[['a', 'b', 'c'], ['e', 'f'], ['g', 'h', 'i']]
I need an algorithm to get all possible combinations of each element from each set.
E.g.
['a', 'e', 'g']
['a', 'f', 'g']
['a', 'f', 'h']
['a', 'f', 'i']
['b', 'e', 'g']
...etc
You can use backtracking to solve this:
def get_perms(arr):
def backtrack(idx, partial_res, res):
if idx == len(arr):
res.append(partial_res[:])
return
for i in range(0, len(arr[idx])):
partial_res.append(arr[idx][i])
backtrack(idx+1, partial_res, res)
partial_res.pop()
res = []
backtrack(0, [], res)
return res
A quick test:
arr = [['a', 'b', 'c'], ['e', 'f'], ['g', 'h', 'i']]
get_perms(arr)
[['a', 'e', 'g'],
['a', 'e', 'h'],
['a', 'e', 'i'],
['a', 'f', 'g'],
['a', 'f', 'h'],
['a', 'f', 'i'],
['b', 'e', 'g'],
['b', 'e', 'h'],
['b', 'e', 'i'],
['b', 'f', 'g'],
['b', 'f', 'h'],
['b', 'f', 'i'],
['c', 'e', 'g'],
['c', 'e', 'h'],
['c', 'e', 'i'],
['c', 'f', 'g'],
['c', 'f', 'h'],
['c', 'f', 'i']]
The algorithm just goes over each inner list and adds each element to partial_res and calls itself recursively while incrementing the index to go to the next inner list.
What you want is a Cartesion product. Use itertools:
from itertools import product
matrix = [['a', 'b', 'c'], ['e', 'f'], ['g', 'h', 'i']]
for res in product(*matrix):
print(list(res))

Use dynamic programming to merge two arrays such that the number of repetitions of the same element is minimised

Let's say we have two arrays m and n containing the characters from the set a, b, c , d, e. Assume each character in the set has a cost associated with it, consider the costs to be a=1, b=3, c=4, d=5, e=7.
for example
m = ['a', 'b', 'c', 'd', 'd', 'e', 'a']
n = ['b', 'b', 'b', 'a', 'c', 'e', 'd']
Suppose we would like to merge m and n to form a larger array s.
An example of s array could be
s = ['a', 'b', 'c', 'd', 'd', 'e', 'a', 'b', 'b', 'b', 'a', 'c', 'e', 'd']
or
s = ['b', 'a', 'd', 'd', 'd', 'b', 'e', 'c', 'b', 'a', 'b', 'a', 'c', 'e']
If there are two or more identical characters adjacent to eachother a penalty is applied which is equal to: number of adjacent characters of the same type * the cost for that character. Consider the second example for s above which contains a sub-array ['d', 'd', 'd']. In this case a penalty of 3*5 will be applied because the cost associated with d is 5 and the number of repetitions of d is 3.
Design a dynamic programming algorithm which minimises the cost associated with s.
Does anyone have any resources, papers, or algorithms they could share to help point me in the right direction?

Research help: Path finding algorithm

Not a technical question, just asking for a point in the right direction for my research.
Are there any models that address the following problem:
Finding the route starting from X that passes through A, B, C in the most efficient order. In the case below, X,A,C,B is the optimum path (12).
Thanks
you may want to look at traveling salesman. There are a lot of resources for how to implement this as it is a very common programming problem.
https://en.wikipedia.org/wiki/Travelling_salesman_problem
This is an implementation of Dijkstra's Algorithm in Python:
def find_all_paths(graph, start, end, path=[]):
required=('A', 'B', 'C')
path = path + [start]
if start == end:
return [path]
if start not in graph:
return []
paths = []
for node in graph[start]:
if node not in path:
newpaths = find_all_paths(graph, node, end, path)
for newpath in newpaths:
if all(e in newpath for e in required):
paths.append(newpath)
return paths
def min_path(graph, start, end):
paths=find_all_paths(graph,start,end)
mt=10**99
mpath=[]
print '\tAll paths:',paths
for path in paths:
t=sum(graph[i][j] for i,j in zip(path,path[1::]))
print '\t\tevaluating:',path, t
if t<mt:
mt=t
mpath=path
e1=' '.join('{}->{}:{}'.format(i,j,graph[i][j]) for i,j in zip(mpath,mpath[1::]))
e2=str(sum(graph[i][j] for i,j in zip(mpath,mpath[1::])))
print 'Best path: '+e1+' Total: '+e2+'\n'
if __name__ == "__main__":
graph = {'X': {'A':5, 'B':8, 'C':10},
'A': {'C':3, 'B':5},
'C': {'A':3, 'B':4},
'B': {'A':5, 'C':4}}
min_path(graph,'X','B')
Prints:
All paths: [['X', 'A', 'C', 'B'], ['X', 'C', 'A', 'B']]
evaluating: ['X', 'A', 'C', 'B'] 12
evaluating: ['X', 'C', 'A', 'B'] 18
Best path: X->A:5 A->C:3 C->B:4 Total: 12
The 'guts' is recursively finding all paths and filtering to only those paths that visit the required nodes ('A', 'B', 'C'). The paths are then summed up to find the minimum path expense.
There are certainly be more efficient approaches but it is hard to be simpler. You asked for a model, so here is a working implementation.

How to calculate total weight of paths of directed weighted graph in DFS in one iteration?

G = (V,E) - a directed weighted graph.
D -> G (w:4)
D -> C (w:2)
D -> E (w:2)
C -> F (w:5)
C -> A (w:4)
B -> D (w:3)
B -> E (w:10)
G -> F (w:1)
E -> G (w:6)
A -> D (w:1)
A -> B (w:2)
picture
I use DFS to find all simple path between START=A node to END=F node:
def find_all_paths(self, start, end, path=[]):
path = path + [start]
if start == end:
return [path]
if start not in self.edges:
return []
paths = []
for node in self.edges[start]:
if node not in path:
paths.extend(self.find_all_paths(node, end, path))
return paths
Result:
['A', 'D', 'G', 'F']
['A', 'D', 'C', 'F']
['A', 'D', 'E', 'G', 'F']
['A', 'B', 'D', 'G', 'F']
['A', 'B', 'D', 'C', 'F']
['A', 'B', 'D', 'E', 'G', 'F']
['A', 'B', 'E', 'G', 'F']
I need to get result like this:
['A', 'D', 'G', 'F'], TOTAL_WEIGHT_OF_PATH = 6
['A', 'D', 'C', 'F'], TOTAL_WEIGHT_OF_PATH = 8
['A', 'D', 'E', 'G', 'F'], TOTAL_WEIGHT_OF_PATH = 10
....
....
Where TOTAL_WEIGHT_OF_PATH is sum of weights for each edge in path.
Of course I could just count the TOTAL_WEIGHT_OF_PATH value after getting result of DFS, but I need to calculate it into DFS steps for cutoff searching in condition based on TOTAL_WEIGHT_OF_PATH (e.g. TOTAL_WEIGHT_OF_PATH should be < MAX_WEIGHT_OF_PATH)
Well, notice that the TOTAL_WEIGT_OF_PATH (TWOP) to any node V (other then the root) is TWOP to the preceding node U plus the weight of the edge (U,V). TWOP to root is 0.
TWOP<sub>V</sub> = TWOP<sub>U</sub> + weight(U,V)
Any time you are expanding a new node on a path, you just need to store the TWOP to this node into it, so you don't need to calculate it every time.
Note, that if you visit a node again, using different path, you need to "calculate" a new weight.

Efficiently generate permutations with ordering restrictions(possibly without backtracking)?

I need to generate permutations of with ordering restrictions on ordering
for example, in the list [A,B,C,D]
A must always come before B, and C must always come before D. There also may or may not be E,F,G... that has no restrictions.
The input would look like this: [[A,B],[C,D],[E],[F]]
Is there a way to do this without computing unnecessary permutations or backtracking?
Normally, a permutations algorithm might look somewhat like this (Python):
def permutations(elements):
if elements:
for i, current in enumerate(elements):
front, back = elements[:i], elements[i+1:]
for perm in permutations(front + back):
yield [current] + perm
else:
yield []
You iterate the list, taking each of the elements as the first element, and combining them with all the permutations of the remaining elements. You can easily modify this so that the elements are actually lists of elements, and instead of just using the current element, you pop the first element off that list and insert the rest back into the recursive call:
def ordered_permutations(elements):
if elements:
for i, current in enumerate(elements):
front, back = elements[:i], elements[i+1:]
first, rest = current[0], current[1:]
for perm in ordered_permutations(front + ([rest] if rest else []) + back):
yield [first] + perm
else:
yield []
Results for ordered_permutations([['A', 'B'], ['C', 'D'], ['E'], ['F']]):
['A', 'B', 'C', 'D', 'E', 'F']
['A', 'B', 'C', 'D', 'F', 'E']
['A', 'B', 'C', 'E', 'D', 'F']
[ ... some 173 more ... ]
['F', 'E', 'A', 'C', 'D', 'B']
['F', 'E', 'C', 'A', 'B', 'D']
['F', 'E', 'C', 'A', 'D', 'B']
['F', 'E', 'C', 'D', 'A', 'B']
Note, though, that this will create a lot of intermediate lists in each recursive call. Instead, you could use stacks, popping the first element off the stack and putting it back on after the recursive calls.
def ordered_permutations_stack(elements):
if any(elements):
for current in elements:
if current:
first = current.pop()
for perm in ordered_permutations_stack(elements):
yield [first] + perm
current.append(first)
else:
yield []
The code might be a bit easier to grasp, too. In this case, you have to reserve the sublists, i.e. call it as ordered_permutations_stack([['B', 'A'], ['D', 'C'], ['E'], ['F']])

Resources