示例#1
0
 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()