def test_getNode_getEdge(self): print("test_getNode_getEdge") graph = DiGraph() graph.add_node(0) graph.add_node(1) graph.add_edge(0, 1, 1) e = graph.getEdge(0, 1) self.assertIsNotNone(e) self.assertEqual(e.getSrc(), 0) self.assertEqual(e.getDest(), 1) self.assertEqual(e.getWeight(), 1) n = graph.getNode(0) self.assertIsNotNone(n) self.assertEqual(n.getKey(), 0)
class TestDiGraph(TestCase): def setUp(self) -> None: self.graph = DiGraph() for vertex in range(7): self.graph.add_node(vertex) self.graph.add_edge(0, 3, 3) self.graph.add_edge(1, 3, 1) self.graph.add_edge(2, 4, 1.99) self.graph.add_edge(3, 2, 7) self.graph.add_edge(3, 4, 1.2) self.graph.add_edge(4, 6, 1.99) self.graph.add_edge(4, 5, 1.8) self.graph.add_edge(6, 2, 1) self.graph.add_edge(6, 5, 9) def test_add_node(self): self.assertFalse(self.graph.add_node(0)) def test_get_node(self): self.assertFalse(self.graph.get_node(100)) self.assertTrue(self.graph.get_node(3)) def test_add_edge(self): self.assertFalse(self.graph.add_edge(1, 4, -9)) self.assertFalse(self.graph.add_edge(93, 4, -9)) self.assertTrue(self.graph.add_edge(1, 3, 0.34)) print(self.graph.edges) def test_all_in_edges_of_node(self): actual = list(self.graph.all_in_edges_of_node(2).keys()) expected = [3, 6] self.assertEqual(actual, expected) self.assertIsNone(self.graph.all_in_edges_of_node(90)) def test_all_out_edges_of_node(self): actual = list(self.graph.all_out_edges_of_node(4)) expected = [6, 5] self.assertEqual(actual, expected) self.assertIsNone(self.graph.all_out_edges_of_node(90)) actual_weight = self.graph.getEdge(4, 6) expected = 1.99 self.assertEqual(actual_weight, expected) def test_remove_edge(self): tup = (3, 2, self.graph.getEdge(3, 2)) self.assertFalse(self.graph.remove_edge(0, 31)) self.assertTrue(self.graph.remove_edge(3, 2)) actual = self.graph.e_size() expected = 8 self.assertEqual(actual, expected) self.assertNotIn(tup, list(self.graph.edges)) def test_remove_node(self): self.assertFalse(self.graph.remove_node(31)) self.graph.remove_node(3) actual = self.graph.v_size() expected = 6 self.assertEqual(actual, expected) self.assertNotIn(3, list(self.graph.adjacency)) self.assertNotIn(3, list(self.graph.vertices)) def test_graph_transpose(self): graph_transpose = self.graph.graph_transpose() actual = list(self.graph.edges) expected = [] for source, destination, weight in graph_transpose.edges: expected.append((destination, source, weight)) self.assertListEqual(actual, expected) def test_v_size(self): actual = self.graph.v_size() expected = 7 self.assertEqual(actual, expected) self.graph.remove_node(900) self.assertEqual(actual, expected) def test_e_size(self): actual = self.graph.e_size() expected = 9 self.assertEqual(actual, expected) self.graph.remove_edge(0, 900) self.assertEqual(actual, expected) def test_get_mc(self): actual = self.graph.mc expected = 16 self.assertEqual(actual, expected) self.graph.remove_node(5) actual = self.graph.mc expected = 19 self.assertEqual(actual, expected) def test_get_all_v(self): actual = list(self.graph.get_all_v()) expected = [0, 1, 2, 3, 4, 5, 6] self.assertListEqual(actual,expected) def test_reset(self): for vertex in self.graph.vertices.values(): vertex.setInfo("visited") self.graph.Reset() actual = 0 for vertex in self.graph.vertices.values(): if vertex.getInfo() == "unvisited": actual = actual + 1 expected = 7 self.assertEqual(actual, expected)
class GraphAlgo(GraphAlgoInterface): def __init__(self, graph=None): if graph is None: self.directed_weighted_graph = DiGraph() else: self.directed_weighted_graph = graph self.parent = {} self.dis = {} def get_graph(self) -> GraphInterface: """ :return: the directed graph on which the algorithm works on. """ return self.directed_weighted_graph def parse_file_name(self, file_name): file_name = file_name.replace('/', '\\') file_name = file_name.replace('\\\\', '\\') file_name = file_name.split('\\') base_path = Path(__file__).parent.parent file_path = "" for i in range(len(file_name) - 1): if not file_name[i].startswith('..'): file_path += file_name[i] + '/' file_path = (base_path / file_path / file_name[len(file_name) - 1]).resolve() return file_path def load_from_json(self, file_name: str) -> bool: """ Loads a graph from a json file. @param file_name: The path to the json file @returns True if the loading was successful, False o.w. """ file_path = self.parse_file_name(file_name) with open(file_path, 'r') as fp: data = json.load(fp) nodes = data["Nodes"] edges = data["Edges"] for n in nodes: if "pos" in n: pos = n['pos'] if type(pos) == str: pos = tuple(pos.split(',')) pos = (float(pos[0]), float(pos[1])) self.directed_weighted_graph.add_node(n["id"], pos) else: self.directed_weighted_graph.add_node(n["id"]) for e in edges: self.directed_weighted_graph.add_edge(e["src"], e["dest"], e["w"]) return True def save_to_json(self, file_name: str) -> bool: """ Saves the graph in JSON format to a file @param file_name: The path to the out file @return: True if the save was successful, False o.w. """ file_name = self.parse_file_name(file_name) try: os.remove(file_name) except OSError: pass edges = self.directed_weighted_graph.get_edges() nodes = self.directed_weighted_graph.get_nodes() json_file = {} jsonEdges = [] jsonNodes = [] for src in edges: for dest in edges[src]: edge = edges[src][dest] parsed_edge = { 'src': edge.getSrc(), 'dest': edge.getDest(), 'w': edge.getWeight() } jsonEdges.append(parsed_edge) for k in nodes: if nodes[k].getLocation(): pos = nodes[k].getLocation() parsed_node = {'pos': pos, 'id': k} else: parsed_node = {'id': k} jsonNodes.append(parsed_node) json_file["Edges"] = jsonEdges json_file["Nodes"] = jsonNodes with open(file_name, 'x') as fp: json.dump(json_file, fp) return True def shortest_path_dist(self, src: int, dest: int) -> float: if src == dest: return 0 self.dijkstra(self.directed_weighted_graph.getNode(src)) return self.dis[dest] def shortest_path(self, id1: int, id2: int) -> (float, list): """ Returns the shortest path from node id1 to node id2 using Dijkstra's Algorithm @param id1: The start node id @param id2: The end node id @return: The distance of the path, a list of the nodes ids that the path goes through Example: # >>> from GraphAlgo import GraphAlgo # >>> g_algo = GraphAlgo() # >>> g_algo.addNode(0) # >>> g_algo.addNode(1) # >>> g_algo.addNode(2) # >>> g_algo.addEdge(0,1,1) # >>> g_algo.addEdge(1,2,4) # >>> g_algo.shortestPath(0,1) # (1, [0, 1]) # >>> g_algo.shortestPath(0,2) # (5, [0, 1, 2]) Notes: If there is no path between id1 and id2, or one of them dose not exist the function returns (float('inf'),[]) More info: https://en.wikipedia.org/wiki/Dijkstra's_algorithm """ if self.directed_weighted_graph.getNode( id1) is None or self.directed_weighted_graph.getNode( id2) is None: return None self.dijkstra(self.directed_weighted_graph.getNode(id1)) s = self.directed_weighted_graph.getNode(id2) path = [] while s is not None: path.append(s.getKey()) s = self.parent[s.getKey()] path.reverse() weight = self.dis[id2] if weight == float('inf'): return weight, [] return weight, path """ function Dijkstra(Graph, source): create vertex set Q for each vertex v in Graph: // Initialization dist[v] ← INFINITY // Unknown distance from source to v prev[v] ← UNDEFINED // Previous node in optimal path from source add v to Q // All nodes initially in Q (unvisited nodes) dist[source] ← 0 // Distance from source to source while Q is not empty: u ← vertex in Q with min dist[u] // Node with the least distance will be selected first remove u from Q for each neighbor v of u: // where v is still in Q. alt ← dist[u] + length(u, v) if alt < dist[v]: // A shorter path to v has been found dist[v] ← alt prev[v] ← u return dist[], prev[] """ def dijkstra(self, src): self.dis = {} self.parent = {} visited = set() q = [] for n in self.directed_weighted_graph.get_all_v(): self.dis[n.getKey()] = float('inf') self.parent[n.getKey()] = None self.dis[src.getKey()] = float(0) q.append((src.getKey(), 0)) while len(q) > 0 and len( visited) != self.directed_weighted_graph.v_size(): key = q.pop(0)[0] if key not in visited: for v in self.directed_weighted_graph.getNode( key).getOutEdges(): if v.getDest() not in visited: if self.directed_weighted_graph.getEdge( key, v.getDest()) is not None: tempSum = self.dis[ key] + self.directed_weighted_graph.getEdge( key, v.getDest()).getWeight() if tempSum < self.dis[v.getDest()]: self.dis[v.getDest()] = tempSum self.parent[v.getDest( )] = self.directed_weighted_graph.getNode(key) q.append((v.getDest(), self.dis[v.getDest()])) sorted(q, key=lambda n: n[1]) visited.add(key) def connected_component(self, id1: int) -> list: """ Finds the Strongly Connected Component(SCC) that node id1 is a part of. @param id1: The node id @return: The list of nodes in the SCC Notes: If the graph is None or id1 is not in the graph, the function should return an empty list [] """ list = [] if self.directed_weighted_graph.getNode(id1) is None: return list list.append(id1) for n in self.directed_weighted_graph.get_all_v(): if self.shortest_path( n.getKey(), id1)[0] != float('inf') and self.shortest_path( id1, n.getKey())[0] != float('inf'): if n.getKey() is not id1: list.append(n.getKey()) return list def connected_components(self) -> List[list]: """ Finds all the Strongly Connected Component(SCC) in the graph. @return: The list all SCC Notes: If the graph is None the function should return an empty list [] """ lists = [] if self.directed_weighted_graph is None: return lists for n in self.directed_weighted_graph.get_all_v(): lists.append(self.connected_component(n.getKey())) return lists def plot_graph(self) -> None: """ Plots the graph. If the nodes have a position, the nodes will be placed there. Otherwise, they will be placed in a random but elegant manner. @return: None """ ax = plt.axes() min_x = float('inf') max_x = -float('inf') min_y = float('inf') max_y = -float('inf') for node in self.directed_weighted_graph.get_nodes(): mynode = self.directed_weighted_graph.getNode(node) node_pos = mynode.getLocation() if node_pos is not None: if node_pos[0] < min_x: min_x = node_pos[0] if node_pos[1] < min_y: min_y = node_pos[1] if node_pos[0] > max_x: max_x = node_pos[0] if node_pos[1] > max_y: max_y = node_pos[1] else: min_x, max_x, min_y, max_y = 0, 500, 0, 500 plt.xlim(min_x + (min_x * 0.59), max_x + (max_x * 0.59)) plt.ylim(min_y + (min_y * 0.59), (max_y * 0.59) + max_y) edges = self.directed_weighted_graph.get_edges() plotted = [] circles = [] for src in edges: src_node = self.directed_weighted_graph.getNode(src) for dest in edges[src]: dest_node = self.directed_weighted_graph.getNode(dest) src_pos = src_node.getLocation() dest_pos = dest_node.getLocation() if src_pos is None: src_pos = [ random.uniform(min_x, max_x), random.uniform(min_y, max_y) ] src_node.setLocation(src_pos[0], src_pos[1]) if dest_pos is None: dest_pos = [ random.uniform(min_x, max_x), random.uniform(min_y, max_y) ] dest_node.setLocation(dest_pos[0], dest_pos[1]) c1 = plt.Circle((src_pos[0], src_pos[1]), max_x / 100 + max_y / 100, color='r') c2 = plt.Circle((dest_pos[0], dest_pos[1]), max_x / 100 + max_y / 100, color='r') if c1 not in circles: circles.append(c1) if c2 not in circles: circles.append(c2) if (src_pos[0] == dest_pos[0] and src_pos[1] == dest_pos[1]): dest_pos = (dest_pos[0] + 0.1, dest_pos[1] + 0.1) plt.arrow(src_pos[0], src_pos[1], dest_pos[0] - src_pos[0], dest_pos[1] - src_pos[1], head_width=max_x * 0.039, length_includes_head=True, head_length=max_y * 0.039, width=max_y * 0.00002 * max_y, color='black', fc="tan") plt.title('|V|=' + str(self.directed_weighted_graph.v_size()) + ',' + '|E|= ' + str(self.directed_weighted_graph.e_size()) + ')', fontdict={ 'color': 'white', 'fontsize': 19, 'fontweight': 980 }) if (src_pos[0], src_pos[1]) not in plotted: label = ax.annotate(src_node.getKey(), xy=(src_pos[0], src_pos[1]), fontsize=15) plotted.append([src_pos[0], src_pos[1]]) if (dest_pos[0], dest_pos[1]) not in plotted: label = ax.annotate(dest_node.getKey(), xy=(dest_pos[0], dest_pos[1]), fontsize=15) plotted.append([dest_pos[0], dest_pos[1]]) for i in circles: ax.add_artist(i) plt.show()