dsa.graph API documentation

# dsa.graph

Module containing graph classes.

View Source

      1""" Module containing graph classes. """
      2
      3from dsa.queue import Queue
      4
      5class AdjacencyMatrixGraph:
      6    """
      7    An unweighted adjacency matrix graph implementation.
      8
      9    This class allows either directed or undirected representation of a graph.
     10    Vertex labels are string types.
     11    """
     12    def __init__(self, labels: list[str]):
     13        """
     14        Initialize the graph with a list of vertex labels.
     15
     16        Args:
     17            labels (list[str]): List of labels for each vertex.
     18        """
     19        self.labels = labels
     20        self.label_index = { label: index for index, label  in enumerate(labels) }
     21
     22        node_count = len(self.labels)
     23        self.array = [[None for i in range(node_count)] for j in range(node_count)]
     24
     25    def add_edge(self, start_label: str, end_label: str, directed=False):
     26        """
     27        Add an edge in the graph.
     28
     29        Args:
     30            start_label (str): Starting vertex label.
     31            end_label (str): Ending vertex label.
     32            directed (bool): Whether the edge is directed.
     33        """
     34        a = self.label_index[start_label]
     35        b = self.label_index[end_label]
     36        self.array[a][b] = True
     37        if not directed:
     38            self.array[b][a] = True
     39
     40    def add_vertex(self, label: str):
     41        """
     42        Add a vertex to the graph.
     43
     44        Args:
     45            label (str): The vertex label to add.
     46        """
     47        self.labels.append(label)
     48        self.label_index[label] = len(self.labels) - 1
     49        for row in self.array:
     50            row.append(None)
     51        self.array.append([None for i in range(len(self.labels))])
     52
     53    def delete_vertex(self, label: str):
     54        """
     55        Delete a vertex from the graph.
     56
     57        Args:
     58            label (str): The vertex label to delete.
     59        """
     60        index = self.label_index[label]
     61        self.labels.pop(index)
     62        self.label_index = { label: index for index, label in enumerate(self.labels) }
     63        self.array.pop(index)
     64        for row in self.array:
     65            row.pop(index)
     66
     67    def delete_edge(self, start_label: str, end_label: str, directed=False):
     68        """
     69        Delete an edge in the graph.
     70
     71        Args:
     72            start_label (str): Starting vertex label.
     73            end_label (str): Ending vertex label.
     74            directed (bool): Whether the edge is directed.
     75        """
     76        a = self.label_index[start_label]
     77        b = self.label_index[end_label]
     78        if self.array[a][b] is None:
     79            raise KeyError(f"Edge {start_label} to {end_label} does not exist")
     80
     81        self.array[a][b] = None
     82        if not directed:
     83            if self.array[b][a] is None:
     84                raise KeyError(f"Edge {end_label} to {start_label} does not exist")
     85            self.array[b][a] = None
     86
     87    def dfs_traverse(self, start_label: str):
     88        """
     89        Perform depth first traversal in an adjacency matrix
     90
     91        Args:
     92            start_label (str): Starting vertex label.
     93
     94        Returns:
     95            Array with depth first order traversal.
     96        """
     97        return self._df_rec_traverse(start_label, set(), [])
     98
     99    def _df_rec_traverse(self, start_label: str, visited, dfs):
    100        """
    101        Helper method for depth first recursive traversal
    102        """
    103        start_index = self.label_index[start_label]
    104        visited.add(start_label)
    105        dfs.append(self.labels[start_index])
    106
    107        for adj_index, is_connected in enumerate(self.array[start_index]):
    108            adj_label = self.labels[adj_index]
    109            if is_connected and adj_label not in visited:
    110                self._df_rec_traverse(adj_label, visited, dfs)
    111
    112        return dfs
    113
    114    def bfs_traverse(self, start_label: str):
    115        """
    116        Perform breadth first traversal in an adjacency matrix.
    117
    118        Args:
    119            start_label (str): Starting vertex label.
    120
    121        Returns:
    122            Array with breadth first order traversal.
    123        """
    124        bfs = []
    125        q = Queue()
    126        visited = set()
    127
    128        start_index = self.label_index[start_label]
    129        q.enqueue(start_index)
    130        bfs.append(self.labels[start_index])
    131        while not q.is_empty():
    132            current = q.dequeue()
    133            for i in range(len(self.array)):
    134                if start_index != i and self.array[current][i] and (i not in visited):
    135                    visited.add(i)
    136                    q.enqueue(i)
    137                    bfs.append(self.labels[i])
    138        return bfs
    139
    140    def vertices(self) -> list:
    141        """
    142        Return a list of vertex labels of the graph
    143        """
    144        return self.labels
    145
    146    def edges(self) -> list:
    147        """
    148        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    149        """
    150        edges = []
    151        vertex_count = len(self.labels)
    152        for i in range(vertex_count):
    153            for j in range(vertex_count):
    154                if self.array[i][j] and i != j:
    155                    edges.append((self.labels[i], self.labels[j]))
    156
    157        return edges
    158
    159    def undirected_edges(self) -> list:
    160        """
    161        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    162        """
    163        edges = []
    164        vertex_count = len(self.labels)
    165        for i in range(vertex_count):
    166            for j in range(vertex_count):
    167                if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:
    168                    edges.append((self.labels[i], self.labels[j]))
    169
    170        return edges
    171
    172    def is_edge(self, start_label: str, end_label: str) -> bool:
    173        """
    174        Return boolean if an edge exists.
    175
    176        Args:
    177            start_label (str): starting vertex label
    178            end_label (str): starting vertex label
    179
    180        Returns:0
    181            A boolean of whether there is an edge from start to end.
    182        """
    183        start_index = self.label_index[start_label]
    184        end_index = self.label_index[end_label]
    185
    186        return self.array[start_index][end_index] is not None
    187
    188    def __getitem__(self, vertex: str) -> list:
    189        """
    190        Args:
    191            vertex (str): The vertex label.
    192        Returns:
    193            A list of adjacent vertex labels.
    194        """
    195        index = self.label_index[vertex]
    196        return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]]
    197
    198    def print_graph(self):
    199        """
    200        Print the contents of the graph.
    201        """
    202        print("   |", end="")
    203        for label in self.labels:
    204            print(f"{label:^3}", end=" ")
    205        print()
    206        print("----" * (len(self.array) + 1))
    207        for r, row in enumerate(self.array):
    208            label = self.labels[r]
    209            print(f"{label:^3}|", end="")
    210            for col in row:
    211                b = " T " if col else "   "
    212                print(b, end=" ")
    213            print()
    214
    215class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph):
    216    """
    217    A weighted adjacency matrix graph implementation
    218    (allows either directed or undirected representation)
    219    vertex labels are string types
    220    """
    221    def __init__(self, labels):
    222        """
    223        Args:
    224            labels: list of labels for each vertex (string types)
    225        """
    226        super().__init__(labels)
    227
    228    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    229        """
    230        Add an edge to the graph.
    231
    232        Args:
    233            start_label (str): The starting vertex label.
    234            end_label (str): The ending vertex label.
    235            weight: The weight of the vertex.
    236            directed: Whether the edge is directed.
    237        """
    238        a = self.label_index[start_label]
    239        b = self.label_index[end_label]
    240
    241        self.array[a][b] = weight
    242        self.array[a][a] = 0
    243        self.array[b][b] = 0
    244
    245        if not directed:
    246            self.array[b][a] = weight
    247
    248    def print_graph(self):
    249        """
    250        Print the contents of the graph.
    251        """
    252        print("   |", end="")
    253        for label in self.labels:
    254            print(f"{label:>3}", end=" ")
    255        print()
    256        print("----" * (len(self.array) + 1))
    257        for r, row in enumerate(self.array):
    258            label = self.labels[r]
    259            print(f"{label:^3}|", end="")
    260            for col in row:
    261                w = f"{col:3}" if col is not None else "   "
    262                print(w, end=" ")
    263            print()
    264
    265    def edges(self) -> list:
    266        """
    267        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    268        """
    269        edges = []
    270        vertex_count = len(self.labels)
    271        for i in range(vertex_count):
    272            for j in range(vertex_count):
    273                weight = self.array[i][j]
    274                if weight and i != j:
    275                    edges.append((self.labels[i], self.labels[j], weight))
    276
    277        return edges
    278
    279    def undirected_edges(self) -> list:
    280        """
    281        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    282        """
    283        edges = []
    284        vertex_count = len(self.labels)
    285        for i in range(vertex_count):
    286            for j in range(vertex_count):
    287                weight = self.array[i][j]
    288                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:
    289                    edges.append((self.labels[i], self.labels[j], weight))
    290
    291        return edges
    292
    293    def is_edge(self, start_label: str, end_label: str) -> bool:
    294        """
    295        Return boolean if an edge exists.
    296
    297
    298        Args:
    299            start_label (str): Starting vertex label.
    300            end_label (str): Ending vertex label.
    301
    302        Returns:
    303            A boolean of whether there is an edge from start to end.
    304        """
    305        return super().is_edge(start_label, end_label)
    306
    307    def weightx(self, start: str, end: str) -> bool:
    308        """
    309        Return weight of an edge (is this used???)
    310        Args:
    311            start_label: starting vertex label
    312            end_label: starting vertex label
    313
    314        Returns:
    315            weight value of an edge from start to end
    316        """
    317        return super().is_edge(start, end)
    318
    319    def __getitem__(self, vertex: str) -> dict:
    320        """
    321        Args:
    322            vertex: vertex label
    323        Returns:
    324            a dictionary of adjacent vertex labels and weights
    325        """
    326        index = self.label_index[vertex]
    327        return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}
    328
    329class AdjacencyListGraph:
    330    """
    331    A unweighted adjacency list vertex implementation
    332    (allows either directed or undirected representation)
    333    vertex labels are string types
    334    """
    335    def __init__(self):
    336        #: hash table of vertices in graph
    337        self._adjacents = {}
    338
    339    def add_edge(self, start_label: str, end_label: str, directed=False):
    340        """
    341        Add an edge to the graph.
    342
    343        Args:
    344            start_label (str): The label of the starting vertex.
    345            end_label (str): The label of the ending vertex.
    346            directed (bool): Whether the edge is directed.
    347        """
    348        self.add_directed_edge(start_label, end_label)
    349        if not directed:
    350            self.add_directed_edge(end_label, start_label)
    351
    352    def add_directed_edge(self, start_label: str, end_label: str):
    353        """
    354        Add a directed edge to the graph.
    355
    356        Args:
    357            start_label (str): The label of the starting vertex.
    358            end_label (str): The label of the ending vertex.
    359        """
    360        if start_label not in self._adjacents:
    361            self._adjacents[start_label] = [end_label]
    362        else:
    363            if end_label not in self._adjacents[start_label]:
    364                self._adjacents[start_label].append(end_label)
    365        if end_label not in self._adjacents:
    366            self._adjacents[end_label] = []
    367
    368    def delete_edge(self, start_label: str, end_label: str, directed=False):
    369        """
    370        Delete an edge in the graph.
    371
    372        Args:
    373            start_label (str): Starting vertex label.
    374            end_label (str): Ending vertex label.
    375            directed (bool): Whether the edge is directed.
    376        Raises:
    377            KeyError: If the edge does exist.
    378        """
    379        if start_label not in self._adjacents:
    380            raise KeyError(f"Vertex {start_label} does not exist")
    381        if end_label not in self._adjacents[start_label]:
    382            raise KeyError(f"Vertex {end_label} does not exist")
    383        self._adjacents[start_label].remove(end_label)
    384        if not directed:
    385            if end_label not in self._adjacents:
    386                raise KeyError(f"Vertex {end_label} does not exist")
    387            if start_label not in self._adjacents[end_label]:
    388                raise KeyError(f"Vertex {start_label} does not exist")
    389            self._adjacents[end_label].remove(start_label)
    390
    391    def vertices(self) -> list:
    392        """
    393        Return a list of vertex labels of the graph
    394        """
    395        return list(self._adjacents.keys())
    396
    397    def adjacents(self, vertex: str) -> list:
    398        """
    399        Return a list of adjacents of a given vertex
    400
    401        Args:
    402            vertex (str): The label of the vertex.
    403        """
    404        return self._adjacents[vertex]
    405
    406    def dfs_traverse(self, start_label: str):
    407        """
    408        Return a list of vertices through depth first traversal starting at a given vertex.
    409
    410        Args:
    411            start_label (str): The starting vertex label.
    412        Returns:
    413            A list of vertex labels.
    414        """
    415        return self._df_rec_traverse(start_label, set(), [])
    416
    417    def _df_rec_traverse(self, start_label: str, visited, dflist):
    418        """
    419        Helper depth first traversal recursive function.
    420        """
    421        visited.add(start_label)
    422        dflist.append(start_label)
    423
    424        for v in self[start_label]:
    425            if v not in visited:
    426                self._df_rec_traverse(v, visited, dflist)
    427        return dflist
    428
    429    def bfs_traverse(self, start_label: str):
    430        """
    431        Return a list of vertices through breadth first traversal starting at a given vertex
    432        Args:
    433            start_label (str): The starting vertex label.
    434        Returns:
    435            A list of vertex labels.
    436        """
    437        visited = set()
    438        q = Queue()
    439        bflist = []
    440
    441        q.enqueue(start_label)
    442        visited.add(start_label)
    443        bflist.append(start_label)
    444
    445        while len(q) > 0:
    446            current = q.dequeue()
    447
    448            for v in self[current]:
    449                if v not in visited:
    450                    visited.add(v)
    451                    q.enqueue(v)
    452                    bflist.append(v)
    453        return bflist
    454
    455    def dfs(self, start_label:str, end_label:str) -> str:
    456        """
    457        Recursive depth first search.
    458
    459        Args:
    460            start_label (str): The starting vertex label.
    461            end_label (str): The vertex label to search for.
    462
    463        Returns:
    464            A vertex in the graph if found, None otherwise.
    465        """
    466        def dfs_rec(current: str, end: str, visited):
    467            if current == end:
    468                return current
    469
    470            visited.add(current)
    471
    472            for v in self.adjacents(current):
    473                if v not in visited:
    474                    result = dfs_rec(v, end, visited)
    475                    if result is not None:
    476                        return result
    477
    478            return None
    479
    480        return dfs_rec(start_label, end_label, set())
    481
    482    def bfs(self, start_label: str, end_label: str) -> str:
    483        """
    484        Breadth first search.
    485
    486        Args:
    487            start_label (str): The starting vertex label.
    488            end_label (str): The vertex label to search for.
    489
    490        Returns:
    491            A vertex in the graph if found, None otherwise.
    492        """
    493        visited = set()
    494        queue = Queue()
    495
    496        visited.add(start_label)
    497        queue.enqueue(start_label)
    498
    499        while len(queue) > 0:
    500            current = queue.dequeue()
    501
    502            if current == end_label:
    503                return current
    504
    505            for v in self[current]:
    506                if v not in visited:
    507                    visited.add(v)
    508                    queue.enqueue(v)
    509
    510        return None
    511
    512    def __getitem__(self, label: str):
    513        """
    514        Args:
    515            vertex: vertex label
    516        Returns:
    517            a list of adjacent vertex labels
    518        """
    519
    520        return self._adjacents[label]
    521
    522    def is_edge(self, start_label: str, end_label: str) -> bool:
    523        """
    524        Return boolean if an edge exists
    525        Args:
    526            start_label (str): The starting vertex label.
    527            end_label (str): The ending vertex label.
    528
    529        Returns:
    530            A boolean of whether there is an edge from start to end
    531        """
    532        return end_label in self[start_label]
    533
    534    def __len__(self):
    535        """
    536        Return the number of nodes in the graph.
    537        Returns:
    538            int: The number of nodes in the graph.
    539        """
    540        return len(self._adjacents)
    541
    542    def edges(self) -> list:
    543        """
    544        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    545        """
    546        edges = []
    547        for start in self._adjacents.keys():
    548            for end in self.adjacents(start):
    549                if start != end:
    550                    edges.append((start, end))
    551        return edges
    552
    553    def undirected_edges(self) -> list:
    554        """
    555        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    556        """
    557        edges = []
    558        for start in self._adjacents.keys():
    559            for end in self.adjacents(start):
    560                if start != end and (end, start) not in edges:
    561                    edges.append((start, end))
    562        return edges
    563
    564class AdjacencyListWeightedGraph(AdjacencyListGraph):
    565    """
    566    A weighted adjacency list vertex implementation in Python
    567    (allows either directed or undirected representation)
    568    """
    569    def __init__(self):
    570        #: hash table of vertices in graph
    571        self._adjacents = {}
    572
    573    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    574        """
    575        Add an edge to the graph.
    576
    577        Args:
    578            start_label: The starting vertex label.
    579            end_label: The ending vertex label.
    580            weight: The edge weight.
    581            directed: Whether the edge is directed.
    582        """
    583        if start_label not in self._adjacents:
    584            self._adjacents[start_label] = {}
    585
    586        self._adjacents[start_label][end_label] = weight
    587
    588        if not directed:
    589            if end_label not in self._adjacents:
    590                self._adjacents[end_label] = {}
    591            self._adjacents[end_label][start_label] = weight
    592
    593    def delete_edge(self, start_label: str, end_label: str, directed=False):
    594        """
    595        Delete an edge in the graph.
    596
    597        Args:
    598            start_label (str): Starting vertex label.
    599            end_label (str): Ending vertex label.
    600            directed (bool): Whether the edge is directed.
    601
    602        Raises:
    603            KeyError: If the edge does exist.
    604        """
    605        if start_label not in self._adjacents:
    606            raise KeyError(f"Vertex {start_label} does not exist")
    607        if end_label not in self._adjacents[start_label]:
    608            raise KeyError(f"Vertex {end_label} does not exist")
    609        del self._adjacents[start_label][end_label]
    610        if not directed:
    611            if end_label not in self._adjacents:
    612                raise KeyError(f"Vertex {end_label} does not exist")
    613            if start_label not in self._adjacents[end_label]:
    614                raise KeyError(f"Vertex {start_label} does not exist")
    615            del self._adjacents[end_label][start_label]
    616
    617    def adjacents(self, vertex: str) -> list:
    618        """
    619        Return a list of adjacents of a given vertex
    620        Args:
    621            vertex: starting vertex label
    622        """
    623        return self._adjacents[vertex]
    624
    625    def dfs_traverse(self):
    626        """
    627        Perform depth first traversal.
    628        """
    629        return self._df_traverse_rec(self, set())
    630
    631    def _df_traverse_rec(self, vertex, visited=None):
    632        """
    633        helper depth first traversal recursive function
    634        """
    635        visited.add(vertex)
    636
    637        for v in vertex.adjacents:
    638            if v not in visited:
    639                v._df_traverse_rec(v, visited)
    640
    641    def bfs_traverse(self):
    642        """
    643        Perform breadth first traversal.
    644        """
    645        start = self
    646        visited = set()
    647        queue = Queue()
    648
    649        queue.enqueue(start)
    650
    651        while len(queue) > 0:
    652            current = queue.dequeue()
    653
    654            if current not in visited:
    655                visited.add(current)
    656
    657                for v in current.adjacents:
    658                    queue.enqueue(v)
    659
    660    def dfs(self, start_label: str, end_label: str) -> str:
    661        """
    662        Recursive depth first search.
    663
    664        Args:
    665            start_label: The starting vertex label.
    666            end_label: The vertex label to search for.
    667        Returns:
    668            vertex in the graph
    669            none if not found.
    670        """
    671        return self.dfs_rec(start_label, end_label, set())
    672
    673    def dfs_rec(self, current, end_label, visited=None):
    674        """
    675        Helper depth-first search recursive function.
    676
    677        Args:
    678            current: Current vertex
    679            end_label: Target vertex label
    680            visited: Set of visited vertices
    681
    682        Returns:
    683            vertex in the graph if found, None otherwise.
    684        """
    685        if visited is None:
    686            visited = set()
    687
    688        if current == end_label:
    689            return current
    690
    691        visited.add(current)
    692
    693        for v in self.adjacents(current):
    694            if v not in visited:
    695                result = self.dfs_rec(v, end_label, visited)
    696                if result is not None:
    697                    return result
    698
    699        return None
    700
    701
    702    def bfs(self, start_label: str, end_label: str) -> str:
    703        """
    704        Recursive breadth first search.
    705
    706        Args:
    707            end: vertex to search for
    708
    709        Returns:
    710            vertex in the graph
    711            None if not found.
    712        """
    713        visited = set()
    714        queue = Queue()
    715
    716        visited.add(start_label)
    717        queue.enqueue(start_label)
    718
    719        while not queue.is_empty():
    720            current = queue.dequeue()
    721
    722            if current == end_label:
    723                return current
    724
    725            for v in self[current]:
    726                if v not in visited:
    727                    visited.add(v)
    728                    queue.enqueue(v)
    729
    730        return None
    731
    732    def __getitem__(self, vertex: str) -> dict:
    733        """
    734        Args:
    735            vertex: vertex label
    736        Returns:
    737        Return a hashtable of adjacent vertices
    738        """
    739        if vertex not in self._adjacents:
    740            return {}
    741        return self._adjacents[vertex]
    742
    743    def __len__(self):
    744        """
    745        Return the number of nodes in the graph.
    746
    747        Returns:
    748            int: The number of nodes in the graph.
    749        """
    750        return len(self._adjacents)
    751
    752    def edges(self) -> list:
    753        """
    754        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    755        """
    756        edges = []
    757        for start in self._adjacents.keys():
    758            for end in self.adjacents(start):
    759                weight = self[start][end]
    760                if start != end:
    761                    edges.append((start, end, weight))
    762        return edges
    763
    764    def undirected_edges(self) -> list:
    765        """
    766        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    767        """
    768        edges = []
    769        for start in self._adjacents.keys():
    770            for end in self.adjacents(start):
    771                weight = self[start][end]
    772                if start != end and (end, start, weight) not in edges:
    773                    edges.append((start, end, weight))
    774        return edges
    775
    776    def is_edge(self, start_label: str, end_label: str) -> bool:
    777        """
    778        Return boolean if an edge exists
    779        Args:
    780            start_label (str): starting vertex label
    781            end_label (str): starting vertex label
    782
    783        Returns:
    784            A boolean of whether there is an edge from start to end.
    785        """
    786        return end_label in self[start_label]
    787
    788
    789
    790__pdoc__ = {
    791    'AdjacencyMatrixGraph.add_vertex': False,
    792    'AdjacencyMatrixGraph.delete_vertex': False,
    793    'AdjacencyListGraph.add_directed_edge': False,
    794}

class AdjacencyMatrixGraph: View Source

      6class AdjacencyMatrixGraph:
      7    """
      8    An unweighted adjacency matrix graph implementation.
      9
     10    This class allows either directed or undirected representation of a graph.
     11    Vertex labels are string types.
     12    """
     13    def __init__(self, labels: list[str]):
     14        """
     15        Initialize the graph with a list of vertex labels.
     16
     17        Args:
     18            labels (list[str]): List of labels for each vertex.
     19        """
     20        self.labels = labels
     21        self.label_index = { label: index for index, label  in enumerate(labels) }
     22
     23        node_count = len(self.labels)
     24        self.array = [[None for i in range(node_count)] for j in range(node_count)]
     25
     26    def add_edge(self, start_label: str, end_label: str, directed=False):
     27        """
     28        Add an edge in the graph.
     29
     30        Args:
     31            start_label (str): Starting vertex label.
     32            end_label (str): Ending vertex label.
     33            directed (bool): Whether the edge is directed.
     34        """
     35        a = self.label_index[start_label]
     36        b = self.label_index[end_label]
     37        self.array[a][b] = True
     38        if not directed:
     39            self.array[b][a] = True
     40
     41    def add_vertex(self, label: str):
     42        """
     43        Add a vertex to the graph.
     44
     45        Args:
     46            label (str): The vertex label to add.
     47        """
     48        self.labels.append(label)
     49        self.label_index[label] = len(self.labels) - 1
     50        for row in self.array:
     51            row.append(None)
     52        self.array.append([None for i in range(len(self.labels))])
     53
     54    def delete_vertex(self, label: str):
     55        """
     56        Delete a vertex from the graph.
     57
     58        Args:
     59            label (str): The vertex label to delete.
     60        """
     61        index = self.label_index[label]
     62        self.labels.pop(index)
     63        self.label_index = { label: index for index, label in enumerate(self.labels) }
     64        self.array.pop(index)
     65        for row in self.array:
     66            row.pop(index)
     67
     68    def delete_edge(self, start_label: str, end_label: str, directed=False):
     69        """
     70        Delete an edge in the graph.
     71
     72        Args:
     73            start_label (str): Starting vertex label.
     74            end_label (str): Ending vertex label.
     75            directed (bool): Whether the edge is directed.
     76        """
     77        a = self.label_index[start_label]
     78        b = self.label_index[end_label]
     79        if self.array[a][b] is None:
     80            raise KeyError(f"Edge {start_label} to {end_label} does not exist")
     81
     82        self.array[a][b] = None
     83        if not directed:
     84            if self.array[b][a] is None:
     85                raise KeyError(f"Edge {end_label} to {start_label} does not exist")
     86            self.array[b][a] = None
     87
     88    def dfs_traverse(self, start_label: str):
     89        """
     90        Perform depth first traversal in an adjacency matrix
     91
     92        Args:
     93            start_label (str): Starting vertex label.
     94
     95        Returns:
     96            Array with depth first order traversal.
     97        """
     98        return self._df_rec_traverse(start_label, set(), [])
     99
    100    def _df_rec_traverse(self, start_label: str, visited, dfs):
    101        """
    102        Helper method for depth first recursive traversal
    103        """
    104        start_index = self.label_index[start_label]
    105        visited.add(start_label)
    106        dfs.append(self.labels[start_index])
    107
    108        for adj_index, is_connected in enumerate(self.array[start_index]):
    109            adj_label = self.labels[adj_index]
    110            if is_connected and adj_label not in visited:
    111                self._df_rec_traverse(adj_label, visited, dfs)
    112
    113        return dfs
    114
    115    def bfs_traverse(self, start_label: str):
    116        """
    117        Perform breadth first traversal in an adjacency matrix.
    118
    119        Args:
    120            start_label (str): Starting vertex label.
    121
    122        Returns:
    123            Array with breadth first order traversal.
    124        """
    125        bfs = []
    126        q = Queue()
    127        visited = set()
    128
    129        start_index = self.label_index[start_label]
    130        q.enqueue(start_index)
    131        bfs.append(self.labels[start_index])
    132        while not q.is_empty():
    133            current = q.dequeue()
    134            for i in range(len(self.array)):
    135                if start_index != i and self.array[current][i] and (i not in visited):
    136                    visited.add(i)
    137                    q.enqueue(i)
    138                    bfs.append(self.labels[i])
    139        return bfs
    140
    141    def vertices(self) -> list:
    142        """
    143        Return a list of vertex labels of the graph
    144        """
    145        return self.labels
    146
    147    def edges(self) -> list:
    148        """
    149        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    150        """
    151        edges = []
    152        vertex_count = len(self.labels)
    153        for i in range(vertex_count):
    154            for j in range(vertex_count):
    155                if self.array[i][j] and i != j:
    156                    edges.append((self.labels[i], self.labels[j]))
    157
    158        return edges
    159
    160    def undirected_edges(self) -> list:
    161        """
    162        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    163        """
    164        edges = []
    165        vertex_count = len(self.labels)
    166        for i in range(vertex_count):
    167            for j in range(vertex_count):
    168                if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:
    169                    edges.append((self.labels[i], self.labels[j]))
    170
    171        return edges
    172
    173    def is_edge(self, start_label: str, end_label: str) -> bool:
    174        """
    175        Return boolean if an edge exists.
    176
    177        Args:
    178            start_label (str): starting vertex label
    179            end_label (str): starting vertex label
    180
    181        Returns:0
    182            A boolean of whether there is an edge from start to end.
    183        """
    184        start_index = self.label_index[start_label]
    185        end_index = self.label_index[end_label]
    186
    187        return self.array[start_index][end_index] is not None
    188
    189    def __getitem__(self, vertex: str) -> list:
    190        """
    191        Args:
    192            vertex (str): The vertex label.
    193        Returns:
    194            A list of adjacent vertex labels.
    195        """
    196        index = self.label_index[vertex]
    197        return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]]
    198
    199    def print_graph(self):
    200        """
    201        Print the contents of the graph.
    202        """
    203        print("   |", end="")
    204        for label in self.labels:
    205            print(f"{label:^3}", end=" ")
    206        print()
    207        print("----" * (len(self.array) + 1))
    208        for r, row in enumerate(self.array):
    209            label = self.labels[r]
    210            print(f"{label:^3}|", end="")
    211            for col in row:
    212                b = " T " if col else "   "
    213                print(b, end=" ")
    214            print()

An unweighted adjacency matrix graph implementation.

This class allows either directed or undirected representation of a graph. Vertex labels are string types.

AdjacencyMatrixGraph(labels: list[str]) View Source

    13    def __init__(self, labels: list[str]):
    14        """
    15        Initialize the graph with a list of vertex labels.
    16
    17        Args:
    18            labels (list[str]): List of labels for each vertex.
    19        """
    20        self.labels = labels
    21        self.label_index = { label: index for index, label  in enumerate(labels) }
    22
    23        node_count = len(self.labels)
    24        self.array = [[None for i in range(node_count)] for j in range(node_count)]

Initialize the graph with a list of vertex labels.

Args: labels (list[str]): List of labels for each vertex.

labels
label_index
array
def add_edge(self, start_label: str, end_label: str, directed=False): View Source

    26    def add_edge(self, start_label: str, end_label: str, directed=False):
    27        """
    28        Add an edge in the graph.
    29
    30        Args:
    31            start_label (str): Starting vertex label.
    32            end_label (str): Ending vertex label.
    33            directed (bool): Whether the edge is directed.
    34        """
    35        a = self.label_index[start_label]
    36        b = self.label_index[end_label]
    37        self.array[a][b] = True
    38        if not directed:
    39            self.array[b][a] = True

Add an edge in the graph.

Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.

def add_vertex(self, label: str): View Source

    41    def add_vertex(self, label: str):
    42        """
    43        Add a vertex to the graph.
    44
    45        Args:
    46            label (str): The vertex label to add.
    47        """
    48        self.labels.append(label)
    49        self.label_index[label] = len(self.labels) - 1
    50        for row in self.array:
    51            row.append(None)
    52        self.array.append([None for i in range(len(self.labels))])

Add a vertex to the graph.

Args: label (str): The vertex label to add.

def delete_vertex(self, label: str): View Source

    54    def delete_vertex(self, label: str):
    55        """
    56        Delete a vertex from the graph.
    57
    58        Args:
    59            label (str): The vertex label to delete.
    60        """
    61        index = self.label_index[label]
    62        self.labels.pop(index)
    63        self.label_index = { label: index for index, label in enumerate(self.labels) }
    64        self.array.pop(index)
    65        for row in self.array:
    66            row.pop(index)

Delete a vertex from the graph.

Args: label (str): The vertex label to delete.

def delete_edge(self, start_label: str, end_label: str, directed=False): View Source

    68    def delete_edge(self, start_label: str, end_label: str, directed=False):
    69        """
    70        Delete an edge in the graph.
    71
    72        Args:
    73            start_label (str): Starting vertex label.
    74            end_label (str): Ending vertex label.
    75            directed (bool): Whether the edge is directed.
    76        """
    77        a = self.label_index[start_label]
    78        b = self.label_index[end_label]
    79        if self.array[a][b] is None:
    80            raise KeyError(f"Edge {start_label} to {end_label} does not exist")
    81
    82        self.array[a][b] = None
    83        if not directed:
    84            if self.array[b][a] is None:
    85                raise KeyError(f"Edge {end_label} to {start_label} does not exist")
    86            self.array[b][a] = None

Delete an edge in the graph.

Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.

def dfs_traverse(self, start_label: str): View Source

    88    def dfs_traverse(self, start_label: str):
    89        """
    90        Perform depth first traversal in an adjacency matrix
    91
    92        Args:
    93            start_label (str): Starting vertex label.
    94
    95        Returns:
    96            Array with depth first order traversal.
    97        """
    98        return self._df_rec_traverse(start_label, set(), [])

Perform depth first traversal in an adjacency matrix

Args: start_label (str): Starting vertex label.

Returns: Array with depth first order traversal.

def bfs_traverse(self, start_label: str): View Source

    115    def bfs_traverse(self, start_label: str):
    116        """
    117        Perform breadth first traversal in an adjacency matrix.
    118
    119        Args:
    120            start_label (str): Starting vertex label.
    121
    122        Returns:
    123            Array with breadth first order traversal.
    124        """
    125        bfs = []
    126        q = Queue()
    127        visited = set()
    128
    129        start_index = self.label_index[start_label]
    130        q.enqueue(start_index)
    131        bfs.append(self.labels[start_index])
    132        while not q.is_empty():
    133            current = q.dequeue()
    134            for i in range(len(self.array)):
    135                if start_index != i and self.array[current][i] and (i not in visited):
    136                    visited.add(i)
    137                    q.enqueue(i)
    138                    bfs.append(self.labels[i])
    139        return bfs

Perform breadth first traversal in an adjacency matrix.

Args: start_label (str): Starting vertex label.

Returns: Array with breadth first order traversal.

def vertices(self) -> list: View Source

    141    def vertices(self) -> list:
    142        """
    143        Return a list of vertex labels of the graph
    144        """
    145        return self.labels

Return a list of vertex labels of the graph

def edges(self) -> list: View Source

    147    def edges(self) -> list:
    148        """
    149        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    150        """
    151        edges = []
    152        vertex_count = len(self.labels)
    153        for i in range(vertex_count):
    154            for j in range(vertex_count):
    155                if self.array[i][j] and i != j:
    156                    edges.append((self.labels[i], self.labels[j]))
    157
    158        return edges

Return a list of edges in the graph. Each edge is represented by a tuple (start, end)

def undirected_edges(self) -> list: View Source

    160    def undirected_edges(self) -> list:
    161        """
    162        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    163        """
    164        edges = []
    165        vertex_count = len(self.labels)
    166        for i in range(vertex_count):
    167            for j in range(vertex_count):
    168                if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:
    169                    edges.append((self.labels[i], self.labels[j]))
    170
    171        return edges

Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)

def is_edge(self, start_label: str, end_label: str) -> bool: View Source

    173    def is_edge(self, start_label: str, end_label: str) -> bool:
    174        """
    175        Return boolean if an edge exists.
    176
    177        Args:
    178            start_label (str): starting vertex label
    179            end_label (str): starting vertex label
    180
    181        Returns:0
    182            A boolean of whether there is an edge from start to end.
    183        """
    184        start_index = self.label_index[start_label]
    185        end_index = self.label_index[end_label]
    186
    187        return self.array[start_index][end_index] is not None

Return boolean if an edge exists.

Args: start_label (str): starting vertex label end_label (str): starting vertex label

Returns:0 A boolean of whether there is an edge from start to end.

def print_graph(self): View Source

    199    def print_graph(self):
    200        """
    201        Print the contents of the graph.
    202        """
    203        print("   |", end="")
    204        for label in self.labels:
    205            print(f"{label:^3}", end=" ")
    206        print()
    207        print("----" * (len(self.array) + 1))
    208        for r, row in enumerate(self.array):
    209            label = self.labels[r]
    210            print(f"{label:^3}|", end="")
    211            for col in row:
    212                b = " T " if col else "   "
    213                print(b, end=" ")
    214            print()

Print the contents of the graph.

class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): View Source

    216class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph):
    217    """
    218    A weighted adjacency matrix graph implementation
    219    (allows either directed or undirected representation)
    220    vertex labels are string types
    221    """
    222    def __init__(self, labels):
    223        """
    224        Args:
    225            labels: list of labels for each vertex (string types)
    226        """
    227        super().__init__(labels)
    228
    229    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    230        """
    231        Add an edge to the graph.
    232
    233        Args:
    234            start_label (str): The starting vertex label.
    235            end_label (str): The ending vertex label.
    236            weight: The weight of the vertex.
    237            directed: Whether the edge is directed.
    238        """
    239        a = self.label_index[start_label]
    240        b = self.label_index[end_label]
    241
    242        self.array[a][b] = weight
    243        self.array[a][a] = 0
    244        self.array[b][b] = 0
    245
    246        if not directed:
    247            self.array[b][a] = weight
    248
    249    def print_graph(self):
    250        """
    251        Print the contents of the graph.
    252        """
    253        print("   |", end="")
    254        for label in self.labels:
    255            print(f"{label:>3}", end=" ")
    256        print()
    257        print("----" * (len(self.array) + 1))
    258        for r, row in enumerate(self.array):
    259            label = self.labels[r]
    260            print(f"{label:^3}|", end="")
    261            for col in row:
    262                w = f"{col:3}" if col is not None else "   "
    263                print(w, end=" ")
    264            print()
    265
    266    def edges(self) -> list:
    267        """
    268        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    269        """
    270        edges = []
    271        vertex_count = len(self.labels)
    272        for i in range(vertex_count):
    273            for j in range(vertex_count):
    274                weight = self.array[i][j]
    275                if weight and i != j:
    276                    edges.append((self.labels[i], self.labels[j], weight))
    277
    278        return edges
    279
    280    def undirected_edges(self) -> list:
    281        """
    282        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    283        """
    284        edges = []
    285        vertex_count = len(self.labels)
    286        for i in range(vertex_count):
    287            for j in range(vertex_count):
    288                weight = self.array[i][j]
    289                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:
    290                    edges.append((self.labels[i], self.labels[j], weight))
    291
    292        return edges
    293
    294    def is_edge(self, start_label: str, end_label: str) -> bool:
    295        """
    296        Return boolean if an edge exists.
    297
    298
    299        Args:
    300            start_label (str): Starting vertex label.
    301            end_label (str): Ending vertex label.
    302
    303        Returns:
    304            A boolean of whether there is an edge from start to end.
    305        """
    306        return super().is_edge(start_label, end_label)
    307
    308    def weightx(self, start: str, end: str) -> bool:
    309        """
    310        Return weight of an edge (is this used???)
    311        Args:
    312            start_label: starting vertex label
    313            end_label: starting vertex label
    314
    315        Returns:
    316            weight value of an edge from start to end
    317        """
    318        return super().is_edge(start, end)
    319
    320    def __getitem__(self, vertex: str) -> dict:
    321        """
    322        Args:
    323            vertex: vertex label
    324        Returns:
    325            a dictionary of adjacent vertex labels and weights
    326        """
    327        index = self.label_index[vertex]
    328        return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}

A weighted adjacency matrix graph implementation (allows either directed or undirected representation) vertex labels are string types

AdjacencyMatrixWeightedGraph(labels) View Source

    222    def __init__(self, labels):
    223        """
    224        Args:
    225            labels: list of labels for each vertex (string types)
    226        """
    227        super().__init__(labels)

Args: labels: list of labels for each vertex (string types)

def add_edge(self, start_label: str, end_label: str, weight, directed=False): View Source

    229    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    230        """
    231        Add an edge to the graph.
    232
    233        Args:
    234            start_label (str): The starting vertex label.
    235            end_label (str): The ending vertex label.
    236            weight: The weight of the vertex.
    237            directed: Whether the edge is directed.
    238        """
    239        a = self.label_index[start_label]
    240        b = self.label_index[end_label]
    241
    242        self.array[a][b] = weight
    243        self.array[a][a] = 0
    244        self.array[b][b] = 0
    245
    246        if not directed:
    247            self.array[b][a] = weight

Add an edge to the graph.

Args: start_label (str): The starting vertex label. end_label (str): The ending vertex label. weight: The weight of the vertex. directed: Whether the edge is directed.

def print_graph(self): View Source

    249    def print_graph(self):
    250        """
    251        Print the contents of the graph.
    252        """
    253        print("   |", end="")
    254        for label in self.labels:
    255            print(f"{label:>3}", end=" ")
    256        print()
    257        print("----" * (len(self.array) + 1))
    258        for r, row in enumerate(self.array):
    259            label = self.labels[r]
    260            print(f"{label:^3}|", end="")
    261            for col in row:
    262                w = f"{col:3}" if col is not None else "   "
    263                print(w, end=" ")
    264            print()

Print the contents of the graph.

def edges(self) -> list: View Source

    266    def edges(self) -> list:
    267        """
    268        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    269        """
    270        edges = []
    271        vertex_count = len(self.labels)
    272        for i in range(vertex_count):
    273            for j in range(vertex_count):
    274                weight = self.array[i][j]
    275                if weight and i != j:
    276                    edges.append((self.labels[i], self.labels[j], weight))
    277
    278        return edges

Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).

def undirected_edges(self) -> list: View Source

    280    def undirected_edges(self) -> list:
    281        """
    282        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    283        """
    284        edges = []
    285        vertex_count = len(self.labels)
    286        for i in range(vertex_count):
    287            for j in range(vertex_count):
    288                weight = self.array[i][j]
    289                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:
    290                    edges.append((self.labels[i], self.labels[j], weight))
    291
    292        return edges

Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).

def is_edge(self, start_label: str, end_label: str) -> bool: View Source

    294    def is_edge(self, start_label: str, end_label: str) -> bool:
    295        """
    296        Return boolean if an edge exists.
    297
    298
    299        Args:
    300            start_label (str): Starting vertex label.
    301            end_label (str): Ending vertex label.
    302
    303        Returns:
    304            A boolean of whether there is an edge from start to end.
    305        """
    306        return super().is_edge(start_label, end_label)

Return boolean if an edge exists.

Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label.

Returns: A boolean of whether there is an edge from start to end.

def weightx(self, start: str, end: str) -> bool: View Source

    308    def weightx(self, start: str, end: str) -> bool:
    309        """
    310        Return weight of an edge (is this used???)
    311        Args:
    312            start_label: starting vertex label
    313            end_label: starting vertex label
    314
    315        Returns:
    316            weight value of an edge from start to end
    317        """
    318        return super().is_edge(start, end)

Return weight of an edge (is this used???) Args: start_label: starting vertex label end_label: starting vertex label

Returns: weight value of an edge from start to end

##### Inherited Members

AdjacencyMatrixGraph
  labels
  label_index
  array
  add_vertex
  delete_vertex
  delete_edge
  dfs_traverse
  bfs_traverse
  vertices

class AdjacencyListGraph: View Source

    330class AdjacencyListGraph:
    331    """
    332    A unweighted adjacency list vertex implementation
    333    (allows either directed or undirected representation)
    334    vertex labels are string types
    335    """
    336    def __init__(self):
    337        #: hash table of vertices in graph
    338        self._adjacents = {}
    339
    340    def add_edge(self, start_label: str, end_label: str, directed=False):
    341        """
    342        Add an edge to the graph.
    343
    344        Args:
    345            start_label (str): The label of the starting vertex.
    346            end_label (str): The label of the ending vertex.
    347            directed (bool): Whether the edge is directed.
    348        """
    349        self.add_directed_edge(start_label, end_label)
    350        if not directed:
    351            self.add_directed_edge(end_label, start_label)
    352
    353    def add_directed_edge(self, start_label: str, end_label: str):
    354        """
    355        Add a directed edge to the graph.
    356
    357        Args:
    358            start_label (str): The label of the starting vertex.
    359            end_label (str): The label of the ending vertex.
    360        """
    361        if start_label not in self._adjacents:
    362            self._adjacents[start_label] = [end_label]
    363        else:
    364            if end_label not in self._adjacents[start_label]:
    365                self._adjacents[start_label].append(end_label)
    366        if end_label not in self._adjacents:
    367            self._adjacents[end_label] = []
    368
    369    def delete_edge(self, start_label: str, end_label: str, directed=False):
    370        """
    371        Delete an edge in the graph.
    372
    373        Args:
    374            start_label (str): Starting vertex label.
    375            end_label (str): Ending vertex label.
    376            directed (bool): Whether the edge is directed.
    377        Raises:
    378            KeyError: If the edge does exist.
    379        """
    380        if start_label not in self._adjacents:
    381            raise KeyError(f"Vertex {start_label} does not exist")
    382        if end_label not in self._adjacents[start_label]:
    383            raise KeyError(f"Vertex {end_label} does not exist")
    384        self._adjacents[start_label].remove(end_label)
    385        if not directed:
    386            if end_label not in self._adjacents:
    387                raise KeyError(f"Vertex {end_label} does not exist")
    388            if start_label not in self._adjacents[end_label]:
    389                raise KeyError(f"Vertex {start_label} does not exist")
    390            self._adjacents[end_label].remove(start_label)
    391
    392    def vertices(self) -> list:
    393        """
    394        Return a list of vertex labels of the graph
    395        """
    396        return list(self._adjacents.keys())
    397
    398    def adjacents(self, vertex: str) -> list:
    399        """
    400        Return a list of adjacents of a given vertex
    401
    402        Args:
    403            vertex (str): The label of the vertex.
    404        """
    405        return self._adjacents[vertex]
    406
    407    def dfs_traverse(self, start_label: str):
    408        """
    409        Return a list of vertices through depth first traversal starting at a given vertex.
    410
    411        Args:
    412            start_label (str): The starting vertex label.
    413        Returns:
    414            A list of vertex labels.
    415        """
    416        return self._df_rec_traverse(start_label, set(), [])
    417
    418    def _df_rec_traverse(self, start_label: str, visited, dflist):
    419        """
    420        Helper depth first traversal recursive function.
    421        """
    422        visited.add(start_label)
    423        dflist.append(start_label)
    424
    425        for v in self[start_label]:
    426            if v not in visited:
    427                self._df_rec_traverse(v, visited, dflist)
    428        return dflist
    429
    430    def bfs_traverse(self, start_label: str):
    431        """
    432        Return a list of vertices through breadth first traversal starting at a given vertex
    433        Args:
    434            start_label (str): The starting vertex label.
    435        Returns:
    436            A list of vertex labels.
    437        """
    438        visited = set()
    439        q = Queue()
    440        bflist = []
    441
    442        q.enqueue(start_label)
    443        visited.add(start_label)
    444        bflist.append(start_label)
    445
    446        while len(q) > 0:
    447            current = q.dequeue()
    448
    449            for v in self[current]:
    450                if v not in visited:
    451                    visited.add(v)
    452                    q.enqueue(v)
    453                    bflist.append(v)
    454        return bflist
    455
    456    def dfs(self, start_label:str, end_label:str) -> str:
    457        """
    458        Recursive depth first search.
    459
    460        Args:
    461            start_label (str): The starting vertex label.
    462            end_label (str): The vertex label to search for.
    463
    464        Returns:
    465            A vertex in the graph if found, None otherwise.
    466        """
    467        def dfs_rec(current: str, end: str, visited):
    468            if current == end:
    469                return current
    470
    471            visited.add(current)
    472
    473            for v in self.adjacents(current):
    474                if v not in visited:
    475                    result = dfs_rec(v, end, visited)
    476                    if result is not None:
    477                        return result
    478
    479            return None
    480
    481        return dfs_rec(start_label, end_label, set())
    482
    483    def bfs(self, start_label: str, end_label: str) -> str:
    484        """
    485        Breadth first search.
    486
    487        Args:
    488            start_label (str): The starting vertex label.
    489            end_label (str): The vertex label to search for.
    490
    491        Returns:
    492            A vertex in the graph if found, None otherwise.
    493        """
    494        visited = set()
    495        queue = Queue()
    496
    497        visited.add(start_label)
    498        queue.enqueue(start_label)
    499
    500        while len(queue) > 0:
    501            current = queue.dequeue()
    502
    503            if current == end_label:
    504                return current
    505
    506            for v in self[current]:
    507                if v not in visited:
    508                    visited.add(v)
    509                    queue.enqueue(v)
    510
    511        return None
    512
    513    def __getitem__(self, label: str):
    514        """
    515        Args:
    516            vertex: vertex label
    517        Returns:
    518            a list of adjacent vertex labels
    519        """
    520
    521        return self._adjacents[label]
    522
    523    def is_edge(self, start_label: str, end_label: str) -> bool:
    524        """
    525        Return boolean if an edge exists
    526        Args:
    527            start_label (str): The starting vertex label.
    528            end_label (str): The ending vertex label.
    529
    530        Returns:
    531            A boolean of whether there is an edge from start to end
    532        """
    533        return end_label in self[start_label]
    534
    535    def __len__(self):
    536        """
    537        Return the number of nodes in the graph.
    538        Returns:
    539            int: The number of nodes in the graph.
    540        """
    541        return len(self._adjacents)
    542
    543    def edges(self) -> list:
    544        """
    545        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    546        """
    547        edges = []
    548        for start in self._adjacents.keys():
    549            for end in self.adjacents(start):
    550                if start != end:
    551                    edges.append((start, end))
    552        return edges
    553
    554    def undirected_edges(self) -> list:
    555        """
    556        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    557        """
    558        edges = []
    559        for start in self._adjacents.keys():
    560            for end in self.adjacents(start):
    561                if start != end and (end, start) not in edges:
    562                    edges.append((start, end))
    563        return edges

A unweighted adjacency list vertex implementation (allows either directed or undirected representation) vertex labels are string types

def add_edge(self, start_label: str, end_label: str, directed=False): View Source

    340    def add_edge(self, start_label: str, end_label: str, directed=False):
    341        """
    342        Add an edge to the graph.
    343
    344        Args:
    345            start_label (str): The label of the starting vertex.
    346            end_label (str): The label of the ending vertex.
    347            directed (bool): Whether the edge is directed.
    348        """
    349        self.add_directed_edge(start_label, end_label)
    350        if not directed:
    351            self.add_directed_edge(end_label, start_label)

Add an edge to the graph.

Args: start_label (str): The label of the starting vertex. end_label (str): The label of the ending vertex. directed (bool): Whether the edge is directed.

def add_directed_edge(self, start_label: str, end_label: str): View Source

    353    def add_directed_edge(self, start_label: str, end_label: str):
    354        """
    355        Add a directed edge to the graph.
    356
    357        Args:
    358            start_label (str): The label of the starting vertex.
    359            end_label (str): The label of the ending vertex.
    360        """
    361        if start_label not in self._adjacents:
    362            self._adjacents[start_label] = [end_label]
    363        else:
    364            if end_label not in self._adjacents[start_label]:
    365                self._adjacents[start_label].append(end_label)
    366        if end_label not in self._adjacents:
    367            self._adjacents[end_label] = []

Add a directed edge to the graph.

Args: start_label (str): The label of the starting vertex. end_label (str): The label of the ending vertex.

def delete_edge(self, start_label: str, end_label: str, directed=False): View Source

    369    def delete_edge(self, start_label: str, end_label: str, directed=False):
    370        """
    371        Delete an edge in the graph.
    372
    373        Args:
    374            start_label (str): Starting vertex label.
    375            end_label (str): Ending vertex label.
    376            directed (bool): Whether the edge is directed.
    377        Raises:
    378            KeyError: If the edge does exist.
    379        """
    380        if start_label not in self._adjacents:
    381            raise KeyError(f"Vertex {start_label} does not exist")
    382        if end_label not in self._adjacents[start_label]:
    383            raise KeyError(f"Vertex {end_label} does not exist")
    384        self._adjacents[start_label].remove(end_label)
    385        if not directed:
    386            if end_label not in self._adjacents:
    387                raise KeyError(f"Vertex {end_label} does not exist")
    388            if start_label not in self._adjacents[end_label]:
    389                raise KeyError(f"Vertex {start_label} does not exist")
    390            self._adjacents[end_label].remove(start_label)

Delete an edge in the graph.

Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed. Raises: KeyError: If the edge does exist.

def vertices(self) -> list: View Source

    392    def vertices(self) -> list:
    393        """
    394        Return a list of vertex labels of the graph
    395        """
    396        return list(self._adjacents.keys())

Return a list of vertex labels of the graph

def adjacents(self, vertex: str) -> list: View Source

    398    def adjacents(self, vertex: str) -> list:
    399        """
    400        Return a list of adjacents of a given vertex
    401
    402        Args:
    403            vertex (str): The label of the vertex.
    404        """
    405        return self._adjacents[vertex]

Return a list of adjacents of a given vertex

Args: vertex (str): The label of the vertex.

def dfs_traverse(self, start_label: str): View Source

    407    def dfs_traverse(self, start_label: str):
    408        """
    409        Return a list of vertices through depth first traversal starting at a given vertex.
    410
    411        Args:
    412            start_label (str): The starting vertex label.
    413        Returns:
    414            A list of vertex labels.
    415        """
    416        return self._df_rec_traverse(start_label, set(), [])

Return a list of vertices through depth first traversal starting at a given vertex.

Args: start_label (str): The starting vertex label. Returns: A list of vertex labels.

def bfs_traverse(self, start_label: str): View Source

    430    def bfs_traverse(self, start_label: str):
    431        """
    432        Return a list of vertices through breadth first traversal starting at a given vertex
    433        Args:
    434            start_label (str): The starting vertex label.
    435        Returns:
    436            A list of vertex labels.
    437        """
    438        visited = set()
    439        q = Queue()
    440        bflist = []
    441
    442        q.enqueue(start_label)
    443        visited.add(start_label)
    444        bflist.append(start_label)
    445
    446        while len(q) > 0:
    447            current = q.dequeue()
    448
    449            for v in self[current]:
    450                if v not in visited:
    451                    visited.add(v)
    452                    q.enqueue(v)
    453                    bflist.append(v)
    454        return bflist

Return a list of vertices through breadth first traversal starting at a given vertex Args: start_label (str): The starting vertex label. Returns: A list of vertex labels.

def dfs(self, start_label: str, end_label: str) -> str: View Source

    456    def dfs(self, start_label:str, end_label:str) -> str:
    457        """
    458        Recursive depth first search.
    459
    460        Args:
    461            start_label (str): The starting vertex label.
    462            end_label (str): The vertex label to search for.
    463
    464        Returns:
    465            A vertex in the graph if found, None otherwise.
    466        """
    467        def dfs_rec(current: str, end: str, visited):
    468            if current == end:
    469                return current
    470
    471            visited.add(current)
    472
    473            for v in self.adjacents(current):
    474                if v not in visited:
    475                    result = dfs_rec(v, end, visited)
    476                    if result is not None:
    477                        return result
    478
    479            return None
    480
    481        return dfs_rec(start_label, end_label, set())

Recursive depth first search.

Args: start_label (str): The starting vertex label. end_label (str): The vertex label to search for.

Returns: A vertex in the graph if found, None otherwise.

def bfs(self, start_label: str, end_label: str) -> str: View Source

    483    def bfs(self, start_label: str, end_label: str) -> str:
    484        """
    485        Breadth first search.
    486
    487        Args:
    488            start_label (str): The starting vertex label.
    489            end_label (str): The vertex label to search for.
    490
    491        Returns:
    492            A vertex in the graph if found, None otherwise.
    493        """
    494        visited = set()
    495        queue = Queue()
    496
    497        visited.add(start_label)
    498        queue.enqueue(start_label)
    499
    500        while len(queue) > 0:
    501            current = queue.dequeue()
    502
    503            if current == end_label:
    504                return current
    505
    506            for v in self[current]:
    507                if v not in visited:
    508                    visited.add(v)
    509                    queue.enqueue(v)
    510
    511        return None

Breadth first search.

Args: start_label (str): The starting vertex label. end_label (str): The vertex label to search for.

Returns: A vertex in the graph if found, None otherwise.

def is_edge(self, start_label: str, end_label: str) -> bool: View Source

    523    def is_edge(self, start_label: str, end_label: str) -> bool:
    524        """
    525        Return boolean if an edge exists
    526        Args:
    527            start_label (str): The starting vertex label.
    528            end_label (str): The ending vertex label.
    529
    530        Returns:
    531            A boolean of whether there is an edge from start to end
    532        """
    533        return end_label in self[start_label]

Return boolean if an edge exists Args: start_label (str): The starting vertex label. end_label (str): The ending vertex label.

Returns: A boolean of whether there is an edge from start to end

def edges(self) -> list: View Source

    543    def edges(self) -> list:
    544        """
    545        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    546        """
    547        edges = []
    548        for start in self._adjacents.keys():
    549            for end in self.adjacents(start):
    550                if start != end:
    551                    edges.append((start, end))
    552        return edges

Return a list of edges in the graph. Each edge is represented by a tuple (start, end)

def undirected_edges(self) -> list: View Source

    554    def undirected_edges(self) -> list:
    555        """
    556        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    557        """
    558        edges = []
    559        for start in self._adjacents.keys():
    560            for end in self.adjacents(start):
    561                if start != end and (end, start) not in edges:
    562                    edges.append((start, end))
    563        return edges

Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)

class AdjacencyListWeightedGraph(AdjacencyListGraph): View Source

    565class AdjacencyListWeightedGraph(AdjacencyListGraph):
    566    """
    567    A weighted adjacency list vertex implementation in Python
    568    (allows either directed or undirected representation)
    569    """
    570    def __init__(self):
    571        #: hash table of vertices in graph
    572        self._adjacents = {}
    573
    574    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    575        """
    576        Add an edge to the graph.
    577
    578        Args:
    579            start_label: The starting vertex label.
    580            end_label: The ending vertex label.
    581            weight: The edge weight.
    582            directed: Whether the edge is directed.
    583        """
    584        if start_label not in self._adjacents:
    585            self._adjacents[start_label] = {}
    586
    587        self._adjacents[start_label][end_label] = weight
    588
    589        if not directed:
    590            if end_label not in self._adjacents:
    591                self._adjacents[end_label] = {}
    592            self._adjacents[end_label][start_label] = weight
    593
    594    def delete_edge(self, start_label: str, end_label: str, directed=False):
    595        """
    596        Delete an edge in the graph.
    597
    598        Args:
    599            start_label (str): Starting vertex label.
    600            end_label (str): Ending vertex label.
    601            directed (bool): Whether the edge is directed.
    602
    603        Raises:
    604            KeyError: If the edge does exist.
    605        """
    606        if start_label not in self._adjacents:
    607            raise KeyError(f"Vertex {start_label} does not exist")
    608        if end_label not in self._adjacents[start_label]:
    609            raise KeyError(f"Vertex {end_label} does not exist")
    610        del self._adjacents[start_label][end_label]
    611        if not directed:
    612            if end_label not in self._adjacents:
    613                raise KeyError(f"Vertex {end_label} does not exist")
    614            if start_label not in self._adjacents[end_label]:
    615                raise KeyError(f"Vertex {start_label} does not exist")
    616            del self._adjacents[end_label][start_label]
    617
    618    def adjacents(self, vertex: str) -> list:
    619        """
    620        Return a list of adjacents of a given vertex
    621        Args:
    622            vertex: starting vertex label
    623        """
    624        return self._adjacents[vertex]
    625
    626    def dfs_traverse(self):
    627        """
    628        Perform depth first traversal.
    629        """
    630        return self._df_traverse_rec(self, set())
    631
    632    def _df_traverse_rec(self, vertex, visited=None):
    633        """
    634        helper depth first traversal recursive function
    635        """
    636        visited.add(vertex)
    637
    638        for v in vertex.adjacents:
    639            if v not in visited:
    640                v._df_traverse_rec(v, visited)
    641
    642    def bfs_traverse(self):
    643        """
    644        Perform breadth first traversal.
    645        """
    646        start = self
    647        visited = set()
    648        queue = Queue()
    649
    650        queue.enqueue(start)
    651
    652        while len(queue) > 0:
    653            current = queue.dequeue()
    654
    655            if current not in visited:
    656                visited.add(current)
    657
    658                for v in current.adjacents:
    659                    queue.enqueue(v)
    660
    661    def dfs(self, start_label: str, end_label: str) -> str:
    662        """
    663        Recursive depth first search.
    664
    665        Args:
    666            start_label: The starting vertex label.
    667            end_label: The vertex label to search for.
    668        Returns:
    669            vertex in the graph
    670            none if not found.
    671        """
    672        return self.dfs_rec(start_label, end_label, set())
    673
    674    def dfs_rec(self, current, end_label, visited=None):
    675        """
    676        Helper depth-first search recursive function.
    677
    678        Args:
    679            current: Current vertex
    680            end_label: Target vertex label
    681            visited: Set of visited vertices
    682
    683        Returns:
    684            vertex in the graph if found, None otherwise.
    685        """
    686        if visited is None:
    687            visited = set()
    688
    689        if current == end_label:
    690            return current
    691
    692        visited.add(current)
    693
    694        for v in self.adjacents(current):
    695            if v not in visited:
    696                result = self.dfs_rec(v, end_label, visited)
    697                if result is not None:
    698                    return result
    699
    700        return None
    701
    702
    703    def bfs(self, start_label: str, end_label: str) -> str:
    704        """
    705        Recursive breadth first search.
    706
    707        Args:
    708            end: vertex to search for
    709
    710        Returns:
    711            vertex in the graph
    712            None if not found.
    713        """
    714        visited = set()
    715        queue = Queue()
    716
    717        visited.add(start_label)
    718        queue.enqueue(start_label)
    719
    720        while not queue.is_empty():
    721            current = queue.dequeue()
    722
    723            if current == end_label:
    724                return current
    725
    726            for v in self[current]:
    727                if v not in visited:
    728                    visited.add(v)
    729                    queue.enqueue(v)
    730
    731        return None
    732
    733    def __getitem__(self, vertex: str) -> dict:
    734        """
    735        Args:
    736            vertex: vertex label
    737        Returns:
    738        Return a hashtable of adjacent vertices
    739        """
    740        if vertex not in self._adjacents:
    741            return {}
    742        return self._adjacents[vertex]
    743
    744    def __len__(self):
    745        """
    746        Return the number of nodes in the graph.
    747
    748        Returns:
    749            int: The number of nodes in the graph.
    750        """
    751        return len(self._adjacents)
    752
    753    def edges(self) -> list:
    754        """
    755        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    756        """
    757        edges = []
    758        for start in self._adjacents.keys():
    759            for end in self.adjacents(start):
    760                weight = self[start][end]
    761                if start != end:
    762                    edges.append((start, end, weight))
    763        return edges
    764
    765    def undirected_edges(self) -> list:
    766        """
    767        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    768        """
    769        edges = []
    770        for start in self._adjacents.keys():
    771            for end in self.adjacents(start):
    772                weight = self[start][end]
    773                if start != end and (end, start, weight) not in edges:
    774                    edges.append((start, end, weight))
    775        return edges
    776
    777    def is_edge(self, start_label: str, end_label: str) -> bool:
    778        """
    779        Return boolean if an edge exists
    780        Args:
    781            start_label (str): starting vertex label
    782            end_label (str): starting vertex label
    783
    784        Returns:
    785            A boolean of whether there is an edge from start to end.
    786        """
    787        return end_label in self[start_label]

A weighted adjacency list vertex implementation in Python (allows either directed or undirected representation)

def add_edge(self, start_label: str, end_label: str, weight, directed=False): View Source

    574    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    575        """
    576        Add an edge to the graph.
    577
    578        Args:
    579            start_label: The starting vertex label.
    580            end_label: The ending vertex label.
    581            weight: The edge weight.
    582            directed: Whether the edge is directed.
    583        """
    584        if start_label not in self._adjacents:
    585            self._adjacents[start_label] = {}
    586
    587        self._adjacents[start_label][end_label] = weight
    588
    589        if not directed:
    590            if end_label not in self._adjacents:
    591                self._adjacents[end_label] = {}
    592            self._adjacents[end_label][start_label] = weight

Add an edge to the graph.

Args: start_label: The starting vertex label. end_label: The ending vertex label. weight: The edge weight. directed: Whether the edge is directed.

def delete_edge(self, start_label: str, end_label: str, directed=False): View Source

    594    def delete_edge(self, start_label: str, end_label: str, directed=False):
    595        """
    596        Delete an edge in the graph.
    597
    598        Args:
    599            start_label (str): Starting vertex label.
    600            end_label (str): Ending vertex label.
    601            directed (bool): Whether the edge is directed.
    602
    603        Raises:
    604            KeyError: If the edge does exist.
    605        """
    606        if start_label not in self._adjacents:
    607            raise KeyError(f"Vertex {start_label} does not exist")
    608        if end_label not in self._adjacents[start_label]:
    609            raise KeyError(f"Vertex {end_label} does not exist")
    610        del self._adjacents[start_label][end_label]
    611        if not directed:
    612            if end_label not in self._adjacents:
    613                raise KeyError(f"Vertex {end_label} does not exist")
    614            if start_label not in self._adjacents[end_label]:
    615                raise KeyError(f"Vertex {start_label} does not exist")
    616            del self._adjacents[end_label][start_label]

Delete an edge in the graph.

Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.

Raises: KeyError: If the edge does exist.

def adjacents(self, vertex: str) -> list: View Source

    618    def adjacents(self, vertex: str) -> list:
    619        """
    620        Return a list of adjacents of a given vertex
    621        Args:
    622            vertex: starting vertex label
    623        """
    624        return self._adjacents[vertex]

Return a list of adjacents of a given vertex Args: vertex: starting vertex label

def dfs_traverse(self): View Source

    626    def dfs_traverse(self):
    627        """
    628        Perform depth first traversal.
    629        """
    630        return self._df_traverse_rec(self, set())

Perform depth first traversal.

def bfs_traverse(self): View Source

    642    def bfs_traverse(self):
    643        """
    644        Perform breadth first traversal.
    645        """
    646        start = self
    647        visited = set()
    648        queue = Queue()
    649
    650        queue.enqueue(start)
    651
    652        while len(queue) > 0:
    653            current = queue.dequeue()
    654
    655            if current not in visited:
    656                visited.add(current)
    657
    658                for v in current.adjacents:
    659                    queue.enqueue(v)

Perform breadth first traversal.

def dfs(self, start_label: str, end_label: str) -> str: View Source

    661    def dfs(self, start_label: str, end_label: str) -> str:
    662        """
    663        Recursive depth first search.
    664
    665        Args:
    666            start_label: The starting vertex label.
    667            end_label: The vertex label to search for.
    668        Returns:
    669            vertex in the graph
    670            none if not found.
    671        """
    672        return self.dfs_rec(start_label, end_label, set())

Recursive depth first search.

Args: start_label: The starting vertex label. end_label: The vertex label to search for. Returns: vertex in the graph none if not found.

def dfs_rec(self, current, end_label, visited=None): View Source

    674    def dfs_rec(self, current, end_label, visited=None):
    675        """
    676        Helper depth-first search recursive function.
    677
    678        Args:
    679            current: Current vertex
    680            end_label: Target vertex label
    681            visited: Set of visited vertices
    682
    683        Returns:
    684            vertex in the graph if found, None otherwise.
    685        """
    686        if visited is None:
    687            visited = set()
    688
    689        if current == end_label:
    690            return current
    691
    692        visited.add(current)
    693
    694        for v in self.adjacents(current):
    695            if v not in visited:
    696                result = self.dfs_rec(v, end_label, visited)
    697                if result is not None:
    698                    return result
    699
    700        return None

Helper depth-first search recursive function.

Args: current: Current vertex end_label: Target vertex label visited: Set of visited vertices

Returns: vertex in the graph if found, None otherwise.

def bfs(self, start_label: str, end_label: str) -> str: View Source

    703    def bfs(self, start_label: str, end_label: str) -> str:
    704        """
    705        Recursive breadth first search.
    706
    707        Args:
    708            end: vertex to search for
    709
    710        Returns:
    711            vertex in the graph
    712            None if not found.
    713        """
    714        visited = set()
    715        queue = Queue()
    716
    717        visited.add(start_label)
    718        queue.enqueue(start_label)
    719
    720        while not queue.is_empty():
    721            current = queue.dequeue()
    722
    723            if current == end_label:
    724                return current
    725
    726            for v in self[current]:
    727                if v not in visited:
    728                    visited.add(v)
    729                    queue.enqueue(v)
    730
    731        return None

Recursive breadth first search.

Args: end: vertex to search for

Returns: vertex in the graph None if not found.

def edges(self) -> list: View Source

    753    def edges(self) -> list:
    754        """
    755        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    756        """
    757        edges = []
    758        for start in self._adjacents.keys():
    759            for end in self.adjacents(start):
    760                weight = self[start][end]
    761                if start != end:
    762                    edges.append((start, end, weight))
    763        return edges

Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)

def undirected_edges(self) -> list: View Source

    765    def undirected_edges(self) -> list:
    766        """
    767        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    768        """
    769        edges = []
    770        for start in self._adjacents.keys():
    771            for end in self.adjacents(start):
    772                weight = self[start][end]
    773                if start != end and (end, start, weight) not in edges:
    774                    edges.append((start, end, weight))
    775        return edges

Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)

def is_edge(self, start_label: str, end_label: str) -> bool: View Source

    777    def is_edge(self, start_label: str, end_label: str) -> bool:
    778        """
    779        Return boolean if an edge exists
    780        Args:
    781            start_label (str): starting vertex label
    782            end_label (str): starting vertex label
    783
    784        Returns:
    785            A boolean of whether there is an edge from start to end.
    786        """
    787        return end_label in self[start_label]

Return boolean if an edge exists Args: start_label (str): starting vertex label end_label (str): starting vertex label

Returns: A boolean of whether there is an edge from start to end.

##### Inherited Members

AdjacencyListGraph
  add_directed_edge
  vertices