예제 #1
0
 def test_all_out_edges_of_node(self):
     g = DiGraph()
     my_dict = dict()
     for i in range(15):
         g.add_node(i)
     for i in range(9):
         g.add_edge(0, i + 2, i * 5)
         my_dict[i + 2] = i * 5
     self.assertEqual(my_dict, g.all_out_edges_of_node(0))
예제 #2
0
class TestDiGraph(unittest.TestCase):
    def setUp(self) -> None:
        """
        build the central graph for all the tests
        """
        self.graph = DiGraph()

        self.graph.add_node(1)
        self.graph.add_node(2)
        self.graph.add_node(3)
        self.graph.add_node(4)
        self.graph.add_node(5)

        self.graph.add_edge(1, 2, 600)
        self.graph.add_edge(2, 3, 600)
        self.graph.add_edge(3, 1, 600)
        self.graph.add_edge(3, 4, 600)
        self.graph.add_edge(4, 5, 600)
        self.graph.add_edge(5, 4, 600)

    def test_v_size(self):
        self.assertEqual(5, self.graph.v_size())

    def test_e_size(self):
        self.assertEqual(6, self.graph.e_size())

    def test_get_mc(self):
        self.assertEqual(11, self.graph.get_mc())

    def test_removeEdge(self):
        self.graph.remove_edge(3, 1)
        self.graph.remove_edge(2, 3)
        self.graph.remove_edge(5, 4)
        self.assertEqual(3, self.graph.e_size())
        self.graph.add_edge(3, 1, 1.67)
        self.assertEqual(4, self.graph.e_size())

    def test_removeNode(self):
        self.graph.remove_node(1)
        self.graph.remove_node(3)
        self.graph.remove_node(1)
        self.assertEqual(3, self.graph.v_size())

    def test_add_node(self):
        self.graph.add_node(6)
        self.graph.add_node(7)
        self.graph.add_node(8)
        self.graph.add_node(8)
        self.assertEqual(8, self.graph.v_size())

    def test_all_in_edges_of_node(self):
        self.assertTrue(len(self.graph.all_in_edges_of_node(1)) == 1)
        self.assertTrue(len(self.graph.all_in_edges_of_node(2)) == 1)
        self.graph.add_node(6)
        self.graph.add_edge(6, 3, 5.5)
        self.assertTrue(len(self.graph.all_in_edges_of_node(3)) == 2)

    def test_all_out_edges_of_node(self):
        self.assertTrue(len(self.graph.all_out_edges_of_node(2)) == 1)
        self.assertTrue(len(self.graph.all_out_edges_of_node(4)) == 1)
        self.assertTrue(len(self.graph.all_out_edges_of_node(3)) == 2)
예제 #3
0
class GraphAlgo(GraphAlgoInterface):
    """This abstract class represents an interface of a graph algotirhms class."""
    myGraph = DiGraph()
    tropologicalSort = []
    sccList = []

    def __init__(self, graph=myGraph):  # , graph: DiGraph):
        self.myGraph = DiGraph()
        self.myGraph = graph
        self.tropologicalSort = []  # have the list in tropoligical sort
        self.sccList = []
        """Init the graph on which this set of algorithms operates on."""

    def get_graph(self) -> DiGraph:
        return self.myGraph

    def get_graph_in_class(self) -> DiGraph:
        return self.myGraph
        """
        :return: the directed graph on which the algorithm works on.
        """

    def load_from_json(self, file_name: str) -> bool:
        try:
            with open(file_name, "r") as file:
                data = json.load(file)
            self.__init__()
            self.myGraph = DiGraph()
            for n in data.get("Nodes"):
                self.myGraph.add_node(n.get("id"), n.get("pos"))
            for e in data.get("Edges"):
                self.myGraph.add_edge(e.get("src"), e.get("dest"), e.get("w"))
            return True
        except Exception as e:
            return False
        """
        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.
        """

    def save_to_json(self, file_name: str) -> bool:
        try:
            with open(file_name, "w") as file:
                json.dump(self.myGraph.as_dict(), indent=4, fp=file)
            return True
        except Exception as e:
            return False
        """
        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, Flase o.w.
        """

    def shortest_path_list(self, src, dest):
        groupNodes = self.myGraph._nodes  # get and store the graphs' collection of vertices.
        parents = {
        }  # for (space I guess.. wanted to achieve time) complexity reason-
        # a hashmap of parents, to restore the parent of each node in the shortest path
        myListg = []  # create a list for returning the shortest path.
        #     groupNodes[i.getKey()] = i  # for every key in the nodes list put it in the hashmap with its key.

        desty = dest  # for further use

        if (dest == src):  # the path of node from itself to itself is itself.
            myListg.append(groupNodes.get(src))
            return myListg

        if src not in groupNodes.keys() or dest not in groupNodes.keys():
            return myListg

        groupNodesKeys = groupNodes.keys()
        for key in groupNodesKeys:  # initializing- prepearing the nodes for the proccess.
            (groupNodes.get(key)).setTagB(float('inf'))
            groupNodes.get(key).setInfo("")
            # Shortest distance- we set it to infinity cuz we didn't check it yet
            parents[
                key] = None  # None because initially, we don't have any path to reach

        (groupNodes.get(src)).setTagB(0)
        # distance a node to itelf is 0, and it's the distance of src from itself

        q = deque()  # deque
        groupNodesKeys = groupNodes.keys()
        for key in groupNodesKeys:
            # all nodes in the graph are unoptimized - thus are in Q
            # key is Integer
            q.append(groupNodes.get(key))

        minDist = float('inf')  # double
        minKey = -1  # int
        dist = 0  # double
        minNode = None  # node_data

        while q:  # while it's not empty
            for node in q:  # getting the smallest dist node
                # node is type NodeData
                if node.getTagB() <= minDist:
                    minDist = node.getTagB()
                    minKey = node.getKey()
                    minNode = node

            q.remove(minNode)
            # for every neighbor of minKey= neinode
            if (self.myGraph.all_out_edges_of_node(minKey)):
                for key in self.myGraph.all_out_edges_of_node(minKey).keys(
                ):  # where v has not yet been removed from Q.
                    # edge is type edge_data
                    # get node data from edge_data
                    neinode = self.myGraph._nodes.get(
                        key)  # (NodeData) #typed NodeData
                    # we're taking a node from myGraph, which is the neighbor of MINKEY node,
                    # which is the DEST in the edge between MINKEY and DEST.
                    if (
                            neinode in q
                    ):  # where v has not yet been removed from Q.#if it contains the neighbor node
                        dist = minDist + self.myGraph.all_out_edges_of_node(
                            minKey).get(key)
                        if (dist < neinode.getTagB()):
                            (neinode).setTagB(
                                dist
                            )  # it's the path's weight sum, why is it double? #(NodeData)
                            neinode.setInfo(str(minKey))

            minDist = float('inf')
        # done with while loop.
        desty = dest

        while (groupNodes.get(desty)
               ).getTagB() != 0 and groupNodes.get(desty).getInfo() != "":
            # typed NodeData
            # means you haven't yet reached the src because only the src has tag=0 (dist)
            # of 0 from itself.
            # myListg.append(groupNodes.get(desty))
            myListg.append(groupNodes.get(desty))
            desty = int(groupNodes.get(desty).getInfo())  # get the "father"
        # myListg.append(groupNodes.get(src)) <<list of nodes
        myListg.append(groupNodes.get(src))  # list of nodes id
        myListg.reverse(
        )  # reverse the list, 'cuz it came reversed, as we got from the dest to src by parents.
        if myListg[len(myListg) - 1].getKey() != dest:
            myListg.clear()

        return myListg
        """     returns the shortest path between src to dest - as an ordered List of nodes"""

    def shortest_path_dist(self, src, dest):
        if (src == dest):  # the distance from a node to itself is 0.
            return 0

        aj = self.shortest_path_list(src, dest)  # aj is type List
        if not (aj):
            return float(
                'inf')  # EMPTY aj MEANS THERE'S NO PATH FROM SRC TO DEST

        weight = (aj.pop(len(aj) - 1)).getTagB()
        return weight  # with -1 cuz of the edges num, if there are 2 nodes, theres only one edge connecting them
        """returns the length of the shortest path between src to dest"""

    def shortest_path(self, id1: int, id2: int) -> (float, list):
        nodesList = self.shortest_path_list(id1, id2)
        # print("nodes",nodesList)
        nodesKeys = []
        for node in nodesList:
            nodesKeys.append(node.key)
        # print("keys ",nodesKeys)
        dist = self.shortest_path_dist(id1, id2)
        return dist, nodesKeys

    """
    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
    """

    def connected_component(self, id1: int) -> list:
        if self.get_graph() is None or id1 not in self.get_graph().get_all_v(
        ).keys():
            return []
        return self.bfs(self.get_graph(), id1, {})
        """
        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
        """

    def connected_components(self) -> List[list]:
        if self.get_graph() is None or self.get_graph().v_size() == 0:
            return []
        all_the_SCC = []
        has_family = {}
        for key in self.get_graph().get_all_v():
            if key not in has_family.keys():
                all_the_SCC.append(self.bfs(self.get_graph(), key, has_family))
        return all_the_SCC
        """
        Finds all the Strongly Connected Component(SCC) in the graph.
        @return: The list all SCC
        """

    def bfs(self, g: DiGraph(), id1: int, has_family: dict) -> list:
        scc = []
        queue = deque()
        v_list = g.get_all_v()
        u = v_list.get(id1)
        scanned, scanned_reverse = {}, {}
        queue.append(u)
        scc.append(u.key)
        has_family[u.key] = True
        scanned[u] = True
        while queue:
            u = queue.popleft()
            if u.key in g._edges.keys():
                for key in g.all_out_edges_of_node(u.key).keys():
                    v = g.get_all_v().get(key)
                    if v not in scanned:
                        scanned[v] = True
                        queue.append(v)
        queue.append(g.get_all_v().get(id1))
        while queue:
            u = queue.popleft()
            if g.all_in_edges_of_node(u.key) is not None:
                for key in g.all_in_edges_of_node(u.key).keys():
                    v = g.get_all_v().get(key)
                    if v not in scanned_reverse:
                        scanned_reverse[v] = True
                        queue.append(v)
                        if v in scanned.keys() and key not in has_family.keys(
                        ):
                            scc.append(key)
                            has_family[key] = True
        return sorted(list(dict.fromkeys(scc)))

    """BFS algorithm: this method operates BFS twice: one for the original graph, and the
    second time for reverse graph. After this time we get SCC for specific node"""

    def plot_graph(self) -> None:
        fig, axes = plt.subplots(figsize=(8, 6))
        csfont = {'fontname': 'Comic Sans MS'}
        axes.set_title("Graph Plot", **csfont, fontsize=25)

        for node in self.myGraph._nodes.values():
            plt.scatter(node.pos[0], node.pos[1], s=25, color="red")
            plt.text(node.pos[0],
                     node.pos[1] + 0.0001,
                     str(node.key),
                     color="green",
                     fontsize=8)

        for src in self.myGraph._edges.keys():
            for dest in self.myGraph._edges.get(src).keys():
                x1 = self.myGraph._nodes.get(src).pos[0]
                y1 = self.myGraph._nodes.get(src).pos[1]
                x2 = self.myGraph._nodes.get(dest).pos[0]
                y2 = self.myGraph._nodes.get(dest).pos[1]

                if (self.myGraph._edges.get(dest)
                    ):  # if there's a chance for a two directional edge..
                    if (self.myGraph._edges.get(dest).get(src)
                        ):  # if there's a two- directional edge
                        srcNode = self.myGraph._nodes.get(src)
                        destNode = self.myGraph._nodes.get(dest)
                        plt.annotate("",
                                     xy=(destNode.pos[0], destNode.pos[1]),
                                     xytext=(srcNode.pos[0], srcNode.pos[1]),
                                     arrowprops=dict(arrowstyle='<->',
                                                     color='black'))
                    else:
                        # There's only a one directed edge
                        plt.annotate("",
                                     xy=(x2, y2),
                                     xytext=(x1, y1),
                                     arrowprops=dict(arrowstyle='->',
                                                     color='blue'))
                else:  # there's only a one directed edge
                    plt.annotate("",
                                 xy=(x2, y2),
                                 xytext=(x1, y1),
                                 arrowprops=dict(arrowstyle='->',
                                                 color='blue'))

        white_patch = mpatches.Patch(color='blue', label='(one)Directed edge')
        black_patch = mpatches.Patch(color='black', label='Bidirected edge')
        magenta_patch = mpatches.Patch(color='red', label='Node')
        plt.legend(handles=[black_patch, white_patch, magenta_patch])
        # axes.relim()
        # axes.autoscale_view()
        plt.show()
        """