Implementation of Shortest Path Graph in Python Class - python

Hi! I am new to Python and I am struggling for many hours so far with some problem regarding Shortest Path Algorithm implementation in Python.
I am expected to solve some task about finding shortest paths (graph problem) among given persons, and at the end find a common person who connects all of them.
I've made something like this so far:
import itertools
class centralperson():
def initialization(self,N,data,a,b,c):
self.data = data
self.N = N
self.a = a
self.b = b
self.c = c
self.list_of_values = [self.a,self.b,self.c]
self.list_of_paths = []
self.common_person = []
def makeGraph(self):
# Create dict with empty list for each key
graph = {key: [] for key in range(self.N)}
self.graph = graph
for key in self.graph:
for x in self.data:
if key in x:
x = x.copy()
x.remove(key)
self.graph[key].extend(x)
def find_path(self,start, end):
path = []
path = path + [start]
if start == end:
return path
if start not in self.graph.keys():
raise ValueError('No such key in graph!')
for node in self.graph[start]:
if node not in path:
newpath = self.find_path(self.graph, node, end, path)
if newpath: return newpath
return self.list_of_paths.append(path)
def findPaths(self):
for pair in itertools.combinations(self.list_of_values, r=5):
self.find_path(*pair)
def commonperson(self):
list_of_lens = {}
commonalities = set(self.list_of_paths[0]) & set(self.list_of_paths[1]) & set(self.list_of_paths[2])
for i in self.list_of_values:
list_of_lens[i] = (len(self.graph[i]))
if len(commonalities)>1 or len(commonalities)<1:
for k,v in list_of_lens.items():
if v==1 and self.graph[k][0] in commonalities:
output = self.graph[k]
self.common_person.append(output)
else:
output = list(commonalities)
self.common_person.append(output)
return
def printo(self):
#return(self.common_person[0])
return(self.list_of_paths,self.list_of_values)
Description of each function and inputs:
N -> number of unique nodes
a,b,c -> some arbitrarily chosen nodes to find common one among them
initialization -> just initialize our global variables used in other methods, and store the list of outputs
makeGraph -> makes an Adjacency List out of an input.
find_path -> find path between two given nodes (backtracking recursion)
findPaths -> it was expected to call find_path here for every combination of A,B,C i.e A->B, A->C, B->C
commonperson -> expected to find common person from the output of list_of_paths list
printo -> print this common person
Generally It works (I'think) when I'am running each function separately. But, when I try to make a huge class of it, it doesn't work :(
I think the problem is with this recursive function find_path. It is supposed to find a path between two person given, and append the result path to the list with all paths. Yet, as I have 3 different persons, and find_path is a recursive function with only 2 parameters.
Hence, I need to find all paths that connects them (3 paths) and append it to a bigger list list_of_paths. I've created a def findPaths to make use of itertools.combinations and in for loop cal function find_path for every combination of start and end argument of this function, but it seems not to work...
Can you help me with this? Also I don't know how to run all the def functions at once, because honestly I wouldn't like to run all instances of the class separately... My desired version is to:
Provide Input to a class i.e : N,data,a,b,c where N is number of unique nodes, data is just list of list with networks assigned, and A,B,C are my persons.
Get Output: which is a common person for all this 3 persons, (I planned to store it in common_person list.

The code inside you class should be indented, i.e.:
class centralperson:
def __init__(self, ...):
...
def makeGraph(self, ...):
...
instead of
class centralperson:
def __init__(self, ...):
...
def makeGraph(self, ...):
...
Try googling for 'python class examples'. I hope this helps!
It might also be useful to experiment with simpler classes before working on this problem.

itertools.combinations(self.list_of_values, r=5)
returns an empty list, since self.list_of_values only has 3 elements, from which you cannot pick 5.
Perhaps you meant:
itertools.combinations(self.list_of_values, r=2)

Related

Python: How to send list from one method back to another?

I hope I can describe this problem easily enough to be understood. My Python/CS education is not formal and I'm not always sure about my terminology. Bear with me and give me some grace, please.
I have a Class with at least two static methods. One method (A) calls the other (B) on a loop. Inside B, I append elements to a list if certain requirements are met. I want to share this list with the user (this is a command line tool). However, with how things are designed right now I am sharing the list one element on a time per loop. I am not sure how to populate this list fully and get it back to Method A and then share it with the user. If I make the list a constant outside of the methods then method B is unable to see it (??) and append to it. :(
helperclass.py
class HelperClass(object)
def __init__(self)
# { methods-n-code-here}
myclass.py
import helperclass
class MyClass(HelperClass)
#staticmethod
GenerateFoo(params1, params2, params3):
# generate some executable scripts
for x in projects:
# do stuff
MyClass.CreateBar(params1, params2, params3)
for y in other:
# do stuff
return xyz
#staticmethod
CreateBar(params1, params2, params3):
# do stuff
# do more stuff
myList = []
for x in list
if z.isThatThing():
ThatThing = click.confirm(# user prompt: Y/N)
# do this
if not ThatThing:
myList.append(foobar)
# more stuff
# more stuff
if len(myList) > 0:
for a in myList:
print(a)
Anyway, what is it that I do not understand? :(
All you need to do is add return myList at the end of CreateBar() function so that you can get back the list inside the for loop of GenerateFoo() function. And then if you want to concatenate the lists then you can declear a new empty list list = [] and then inside for loop of GenerateFoo() you can write myList = MyClass.CreateBar(params1, params2, params3) to store the list in myList and again to concatenate use list.extend(myList). At the end of GenerateFoo() return the concatenated list using return list.
I have edited your example code below and put ### to new and modified lines to make it easier to find them.
helperclass.py:
class HelperClass(object)
def __init__(self)
# { methods-n-code-here}
myclass.py
import helperclass
class MyClass(HelperClass)
#staticmethod
GenerateFoo(params1, params2, params3):
# generate some executable scripts
list = [] ###
for x in projects:
# do stuff
myList = MyClass.CreateBar(params1, params2, params3) ###
list.extend(myList) ###
for y in other:
# do stuff
return list ###
#staticmethod
CreateBar(params1, params2, params3):
# do stuff
# do more stuff
myList = []
for x in list
if z.isThatThing():
ThatThing = click.confirm(# user prompt: Y/N)
# do this
if not ThatThing:
myList.append(foobar)
return myList ###

Python Temperamentally Accessing Global Object from within a Function (which references that object from within another)

(Edit: Fundamentally my problem is that python is sometimes creating new instances of an object x,which I accessed by another object y, accessed by z instead of editing the original x directly. x and y both belong to a list of global variables, but when I access y via z to access x via y on a subsequent iteration of my recursive algorithm, its information isn't always correctly updated.)
Introduction
I'm writing a recursive function to emulate this version of Dijkstra's algorithm (Problem 2) with input from a CSV file. I have two globals Branches [] and Nodes [] to store all branch and node python objects. (I'll put how everything is initialized below).
When I attempt to change the set of the destination node of my branch from within my function, python generates a new object. It's not that it cannot access global Branches, because it does function correctly when the start node of the branch I am accessing is the same as the origin.
Update: I tried searching through the global list for a node with the label matching the one I wanted to change, but while this correctly changed the set of the global none of the branches referencing that node recognized the change
def dijkstra_algorithm(node_being_investigated, origin_node, destination_node):
...
for branch in shortest_route.requisite_branches:
# finding the new branch added to the route and adding it to set I and its destination to set A
if branch not in branches_in_set_I:
print(f"adding the new branch {branch.info()} to set I")
print(f"Adding the new node {branch.destination.info()} to set A")
print(f"The New Node: {branch.destination} ")
branch.set = "I"
branch.destination.set = "A"
# a redundancy I added just in case the for loop was somehow messing with things
shortest_route.requisite_branches[0].destination.set="A"
if destination in obtain_nodes_of_set("A"):
return shortest_route
else:
return dijkstra_algorithm(shortest_route.requisite_branches[0].destination, origin, destination)
NB: There is one other class Routes which stores a list of branches and their cumulative time. I suspect that since this is the only place some operation is performed on a node or branch, the problem is linked to this somehow. However, since I'm not sure how, here's a small table of contents of all the code snippets I've attached. I can send more (or even the whole file) if need be.
The Code Below
the way I define the Node and Branch classes
the initialization of everything from the CSVs
the way I define the Route class
the search_to_origin function which finds the overall length of various Route options
the piece of the dijkstra_algorithm function which finds the shortest_route inputted to this piece of the algorithm
Defining the Node and Branch classes
class Node:
def __init__(self, label, set):
self.label = label
self.set = set and set or "C"
print(f"\n\n!!!creating new node object with label {self.label}!!!\n\n")
def info(self):
return f"Set {self.set}: [[{self.label}]]"
and:
class Branch:
def __init__(self, origin, destination, duration, set):
self.origin = origin
self.destination = destination
self.duration = duration
self.set = set and set or "III"
def info(self):
return f"Set {self.set}:{self.origin.info()} -> {self.destination.info()} ({self.duration})"
Initializing the global Lists from the CSVs
From what I've checked by printing out the object IDs this seems to be working correctly, but since I'm not actually sure where the problem lies, I'm including it just in case
with open('nodes.csv') as csv_file:
csv_reader = csv.DictReader(csv_file)
for row in csv_reader:
Nodes.append(Node(row["LABEL"], 99999, "C"))
# here all the roads are read into the global list of branches
with open('roads.csv') as csv_file:
csv_reader = csv.DictReader(csv_file)
for row in csv_reader:
branch_info = {}
# getting the actual node objects to attach
for node in Nodes:
if node.label == row["ORIGIN"]:
branch_info['origin'] = node
if node.label == row["DESTINATION"]:
branch_info['destination'] = node
Branches.append(Branch(branch_info['origin'], branch_info['destination'], int(row["DURATION"]), "III"))
The Route Class and the Main Function Operating on It
There is a sub function called by the overall algorithm which traces the route from a list of branches back to a specified origin node object. Afterwards it returns a list of possible_routes to the main dijkstra function, for that function to decide which is quickest.
Route Class Initialization
class Route:
def __init__(self, branch_list, duration):
self.requisite_branches = branch_list
self.duration = duration
def extend_route(self, branch):
self.requisite_branches.append(branch)
self.duration += branch.duration
The function determining routes to the origin
Node that this calls a utility function find_attached_branches which searches for branches with a node as its origin or destination from a specific set.
def search_to_origin(origin, branches, route):
print("\n Searching to origin...")
possible_routes = []
# for each of the branches being investigated
for branch in branches:
# if this branch has not already been investigated in this route
if branch not in route.requisite_branches:
# this is a new individual route being investigated
new_route = copy.deepcopy(route)
new_route.extend_route(branch)
# if the start node has been found this is a possible route
if branch.origin == origin:
print("This branch leads to the origin")
possible_routes.append(new_route)
# if the start node has not been found search for branches preceding this one
else:
branches_to_this_node = find_attached_branches(branch.origin, 'destination, "II")
branches_to_this_node.extend(find_attached_branches(branch.origin, 'destination, "I"))
if len(branches_to_this_node) != 0:
print("this branch does not lead to the origin")
route_to_start = search_to_origin(origin, branches_to_this_node, new_route)
possible_routes.extend(route_to_start)
# return the lengths and requisite branches found
return possible_routes
Finding the Quickest Route to the Origin From a Given Node in B
This portion of the algorithm is done just before the one you see in the introduction section. The way it functions should be independent of whether the shortest route only consists of one branch, but this doesn't seem to be the case. It goes through the following steps:
Looks for all the nodes now in set B.
For each of these nodes it finds all the branches attached to each node in set B.
For all of those branches it looks for routes back to the origin.
From all of the resulting routes it determines the shortest_route
global Nodes
# get all the nodes in set B
nodes_in_set_B = obtain_nodes_of_set("B")
# get all branches in sets I or II
global Branches
branches_in_I_or_II = []
for branch in Branches:
if branch.set != "III": branches_in_I_or_II.append(branch)
# the shortest route found from one of the nodes of set B. Initialized as a large empty route
shortest_route = Route([], 99999)
if nodes_in_set_B is not None:
for node in nodes_in_set_B:
# the branches under consideration are only those to this node in set B
branches_under_consideration = []
for branch in branches_in_I_or_II:
if branch.destination == node: branches_under_consideration.append(branch)
possible_routes = search_to_origin(origin, branches_under_consideration, Route([], 0))
# finding the possible route of minimum length
for route in possible_routes:
if route.duration < shortest_route.duration:
shortest_route = route

Remove router function in python

Hi just wondering if anyone can tell me why this code isn't working
It's giving an internal server error
Thanks
#app.post("/removerouter/")
def removerouter(name: str):
Graph.remove_node(name)
return "success"
And this is the function inside Graph
class Graph:
def __init__(self):
self.nodes = []
self.edges = []
def remove_node(self, name):
self.nodes.remove(name)
for Edges in self.edges:
if name in Edges:
self.edges.remove(Edges)
Based on the code that you posted, I would say that the issue is somehow related to how you remove your edge(s) in the for loop.
When you delete a list element using the remove()function in Python, it changes the remaining element's indexing.
For more details and alternatives, see this SO question.
I also don't understand why you are using an iterator variable called Edges in your for loop. Python variables shall always start with a lowercase letter in order not to clash with any existing (or future) class name.
I would rather do something like this:
class Graph:
def __init__(self):
self.nodes = []
self.edges = []
def remove_node(self, name):
self.nodes.remove(name)
self.edges = [edge for edge in self.edges if name not in edge]
Note that I'm using a list comprehension here to assign a new list to self.edges.
If you want to avoid list comprehension, you could also keep your for loop and first store the indexes of the edges that need to be removed. Then, for each index, you can simply do del self.edges[index].

Removing redundancy between those two functions?

I feel like there is a redundency between:
def _determine_nodes_with_no_predecessors(nodes:List[Node])->List[str]:
"""
Given the list of nodes returns the identifiers of nodes that doesn't have any predecessor.
"""
nodes_ids_without_prdecessors = []
for node in nodes:
if not node.predecessors:
nodes_ids_without_prdecessors.append(node.id_)
return nodes_ids_without_prdecessors
And this one:
def _determine_nodes_with_no_successors(nodes:List[Node])->List[str]:
"""
Given the list of nodes returns the identifiers of nodes that doesn't have any successor.
"""
nodes_ids_without_successors = []
for node in nodes:
if not node.successors:
nodes_ids_without_successors.append(node.id_)
return nodes_ids_without_successors
How to write less code in this case? Is it possible to write only one function? I thought about adding a bool as an argument, something like START = True and then write in if-else statement, but I don't know if it's clean.
If you wanted to generalize this, you could pass in a accessor function. There are multiple ways to do this, but a simple lambda would suffice:
def _determine_nodes_with_no_using(nodes: List[Node], accessor: Callable[[Node], List[Node]])-> List[str]:
"""
Given the list of nodes returns the identifiers of nodes that doesn't have any successor.
"""
nodes_ids_without = []
for node in nodes:
if not accessor(node):
nodes_ids_without.append(node.id_)
return nodes_ids_without
Then
_determine_nodes_with_no_using(nodes, lambda node: node.predecessors)
_determine_nodes_with_no_using(nodes, lambda node: node.successors)
You could also use the operator module:
from operator import attrgetter
_determine_nodes_with_no_using(nodes, attrgetter('successors'))
_determine_nodes_with_no_using(nodes, attrgetter('predecessors'))

Disjoint-Set forests in Python alternate implementation

I'm implementing a disjoint set system in Python, but I've hit a wall. I'm using a tree implementation for the system and am implementing Find(), Merge() and Create() functions for the system.
I am implementing a rank system and path compression for efficiency.
The catch is that these functions must take the set of disjoint sets as a parameter, making traversing hard.
class Node(object):
def __init__(self, value):
self.parent = self
self.value = value
self.rank = 0
def Create(values):
l = [Node(value) for value in values]
return l
The Create function takes in a list of values and returns a list of singular Nodes containing the appropriate data.
I'm thinking the Merge function would look similar to this,
def Merge(set, value1, value2):
value1Root = Find(set, value1)
value2Root = Find(set, value2)
if value1Root == value2Root:
return
if value1Root.rank < value2Root.rank:
value1Root.parent = value2Root
elif value1Root.rank > value2Root.rank:
value2Root.parent = value1Root
else:
value2Root.parent = value1Root
value1Root.rank += 1
but I'm not sure how to implement the Find() function since it is required to take the list of Nodes and a value (not just a node) as the parameters. Find(set, value) would be the prototype.
I understand how to implement path compression when a Node is taken as a parameter for Find(x), but this method is throwing me off.
Any help would be greatly appreciated. Thank you.
Edited for clarification.
The implementation of this data structure becomes simpler when you realize that the operations union and find can also be implemented as methods of a disjoint set forest class, rather than on the individual disjoint sets.
If you can read C++, then have a look at my take on the data structure; it hides the actual sets from the outside world, representing them only as numeric indices in the API. In Python, it would be something like
class DisjSets(object):
def __init__(self, n):
self._parent = range(n)
self._rank = [0] * n
def find(self, i):
if self._parent[i] == i:
return i
else:
self._parent[i] = self.find(self._parent[i])
return self._parent[i]
def union(self, i, j):
root_i = self.find(i)
root_j = self.find(j)
if root_i != root_j:
if self._rank[root_i] < self._rank[root_j]:
self._parent[root_i] = root_j
elif self._rank[root_i] > self._rank[root_j]:
self._parent[root_j] = root_i
else:
self._parent[root_i] = root_j
self._rank[root_j] += 1
(Not tested.)
If you choose not to follow this path, the client of your code will indeed have to have knowledge of Nodes and Find must take a Node argument.
Clearly merge function should be applied to pair of nodes.
So find function should take single node parameter and look like this:
def find(node):
if node.parent != node:
node.parent = find(node.parent)
return node.parent
Also wikipedia has pseudocode that is easily translatable to python.
Find is always done on an item. Find(item) is defined as returning the set to which the item belongs. Merger as such must not take nodes, merge always takes two items/sets. Merge or union (item1, item2) must first find(item1) and find(item2) which will return the sets to which each of these belong. After that the smaller set represented by an up-tree must be added to the taller. When a find is issued, always retrace the path and compress it.
A tested implementation with path compression is here.

Categories

Resources