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 __contains__(self, label: str) -> bool:
    199        """
    200        Args:
    201            label (str): The vertex label.
    202        Returns:
    203            A boolean indicating if the vertex is in the graph.
    204        """
    205        return label in self.label_index
    206
    207    def print_graph(self):
    208        """
    209        Print the contents of the graph.
    210        """
    211        print("   |", end="")
    212        for label in self.labels:
    213            print(f"{label:^3}", end=" ")
    214        print()
    215        print("----" * (len(self.array) + 1))
    216        for r, row in enumerate(self.array):
    217            label = self.labels[r]
    218            print(f"{label:^3}|", end="")
    219            for col in row:
    220                b = " T " if col else "   "
    221                print(b, end=" ")
    222            print()
    223
    224class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph):
    225    """
    226    A weighted adjacency matrix graph implementation
    227    (allows either directed or undirected representation)
    228    vertex labels are string types
    229    """
    230    def __init__(self, labels):
    231        """
    232        Args:
    233            labels: list of labels for each vertex (string types)
    234        """
    235        super().__init__(labels)
    236
    237    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    238        """
    239        Add an edge to the graph.
    240
    241        Args:
    242            start_label (str): The starting vertex label.
    243            end_label (str): The ending vertex label.
    244            weight: The weight of the vertex.
    245            directed: Whether the edge is directed.
    246        """
    247        a = self.label_index[start_label]
    248        b = self.label_index[end_label]
    249
    250        self.array[a][b] = weight
    251        self.array[a][a] = 0
    252        self.array[b][b] = 0
    253
    254        if not directed:
    255            self.array[b][a] = weight
    256
    257    def print_graph(self):
    258        """
    259        Print the contents of the graph.
    260        """
    261        print("   |", end="")
    262        for label in self.labels:
    263            print(f"{label:>3}", end=" ")
    264        print()
    265        print("----" * (len(self.array) + 1))
    266        for r, row in enumerate(self.array):
    267            label = self.labels[r]
    268            print(f"{label:^3}|", end="")
    269            for col in row:
    270                w = f"{col:3}" if col is not None else "   "
    271                print(w, end=" ")
    272            print()
    273
    274    def edges(self) -> list:
    275        """
    276        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    277        """
    278        edges = []
    279        vertex_count = len(self.labels)
    280        for i in range(vertex_count):
    281            for j in range(vertex_count):
    282                weight = self.array[i][j]
    283                if weight and i != j:
    284                    edges.append((self.labels[i], self.labels[j], weight))
    285
    286        return edges
    287
    288    def undirected_edges(self) -> list:
    289        """
    290        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    291        """
    292        edges = []
    293        vertex_count = len(self.labels)
    294        for i in range(vertex_count):
    295            for j in range(vertex_count):
    296                weight = self.array[i][j]
    297                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:
    298                    edges.append((self.labels[i], self.labels[j], weight))
    299
    300        return edges
    301
    302    def is_edge(self, start_label: str, end_label: str) -> bool:
    303        """
    304        Return boolean if an edge exists.
    305
    306
    307        Args:
    308            start_label (str): Starting vertex label.
    309            end_label (str): Ending vertex label.
    310
    311        Returns:
    312            A boolean of whether there is an edge from start to end.
    313        """
    314        return super().is_edge(start_label, end_label)
    315
    316    def weightx(self, start: str, end: str) -> bool:
    317        """
    318        Return weight of an edge (is this used???)
    319        Args:
    320            start_label: starting vertex label
    321            end_label: starting vertex label
    322
    323        Returns:
    324            weight value of an edge from start to end
    325        """
    326        return super().is_edge(start, end)
    327
    328    def __getitem__(self, vertex: str) -> dict:
    329        """
    330        Args:
    331            vertex: vertex label
    332        Returns:
    333            a dictionary of adjacent vertex labels and weights
    334        """
    335        index = self.label_index[vertex]
    336        return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}
    337
    338class AdjacencyListGraph:
    339    """
    340    A unweighted adjacency list vertex implementation
    341    (allows either directed or undirected representation)
    342    vertex labels are string types
    343    """
    344    def __init__(self):
    345        #: hash table of vertices in graph
    346        self._adjacents = {}
    347
    348    def add_edge(self, start_label: str, end_label: str, directed=False):
    349        """
    350        Add an edge to the graph.
    351
    352        Args:
    353            start_label (str): The label of the starting vertex.
    354            end_label (str): The label of the ending vertex.
    355            directed (bool): Whether the edge is directed.
    356        """
    357        self.add_directed_edge(start_label, end_label)
    358        if not directed:
    359            self.add_directed_edge(end_label, start_label)
    360
    361    def add_directed_edge(self, start_label: str, end_label: str):
    362        """
    363        Add a directed edge to the graph.
    364
    365        Args:
    366            start_label (str): The label of the starting vertex.
    367            end_label (str): The label of the ending vertex.
    368        """
    369        if start_label not in self._adjacents:
    370            self._adjacents[start_label] = [end_label]
    371        else:
    372            if end_label not in self._adjacents[start_label]:
    373                self._adjacents[start_label].append(end_label)
    374        if end_label not in self._adjacents:
    375            self._adjacents[end_label] = []
    376
    377    def delete_edge(self, start_label: str, end_label: str, directed=False):
    378        """
    379        Delete an edge in the graph.
    380
    381        Args:
    382            start_label (str): Starting vertex label.
    383            end_label (str): Ending vertex label.
    384            directed (bool): Whether the edge is directed.
    385        Raises:
    386            KeyError: If the edge does exist.
    387        """
    388        if start_label not in self._adjacents:
    389            raise KeyError(f"Vertex {start_label} does not exist")
    390        if end_label not in self._adjacents[start_label]:
    391            raise KeyError(f"Vertex {end_label} does not exist")
    392        self._adjacents[start_label].remove(end_label)
    393        if not directed:
    394            if end_label not in self._adjacents:
    395                raise KeyError(f"Vertex {end_label} does not exist")
    396            if start_label not in self._adjacents[end_label]:
    397                raise KeyError(f"Vertex {start_label} does not exist")
    398            self._adjacents[end_label].remove(start_label)
    399
    400    def vertices(self) -> list:
    401        """
    402        Return a list of vertex labels of the graph
    403        """
    404        return list(self._adjacents.keys())
    405
    406    def adjacents(self, vertex: str) -> list:
    407        """
    408        Return a list of adjacents of a given vertex
    409
    410        Args:
    411            vertex (str): The label of the vertex.
    412        """
    413        return self._adjacents[vertex]
    414
    415    def dfs_traverse(self, start_label: str):
    416        """
    417        Return a list of vertices through depth first traversal starting at a given vertex.
    418
    419        Args:
    420            start_label (str): The starting vertex label.
    421        Returns:
    422            A list of vertex labels.
    423        """
    424        return self._df_rec_traverse(start_label, set(), [])
    425
    426    def _df_rec_traverse(self, start_label: str, visited, dflist):
    427        """
    428        Helper depth first traversal recursive function.
    429        """
    430        visited.add(start_label)
    431        dflist.append(start_label)
    432
    433        for v in self[start_label]:
    434            if v not in visited:
    435                self._df_rec_traverse(v, visited, dflist)
    436        return dflist
    437
    438    def bfs_traverse(self, start_label: str):
    439        """
    440        Return a list of vertices through breadth first traversal starting at a given vertex
    441        Args:
    442            start_label (str): The starting vertex label.
    443        Returns:
    444            A list of vertex labels.
    445        """
    446        visited = set()
    447        q = Queue()
    448        bflist = []
    449
    450        q.enqueue(start_label)
    451        visited.add(start_label)
    452        bflist.append(start_label)
    453
    454        while len(q) > 0:
    455            current = q.dequeue()
    456
    457            for v in self[current]:
    458                if v not in visited:
    459                    visited.add(v)
    460                    q.enqueue(v)
    461                    bflist.append(v)
    462        return bflist
    463
    464    def dfs(self, start_label:str, end_label:str) -> str:
    465        """
    466        Recursive depth first search.
    467
    468        Args:
    469            start_label (str): The starting vertex label.
    470            end_label (str): The vertex label to search for.
    471
    472        Returns:
    473            A vertex in the graph if found, None otherwise.
    474        """
    475        def dfs_rec(current: str, end: str, visited):
    476            if current == end:
    477                return current
    478
    479            visited.add(current)
    480
    481            for v in self.adjacents(current):
    482                if v not in visited:
    483                    result = dfs_rec(v, end, visited)
    484                    if result is not None:
    485                        return result
    486
    487            return None
    488
    489        return dfs_rec(start_label, end_label, set())
    490
    491    def bfs(self, start_label: str, end_label: str) -> str:
    492        """
    493        Breadth first search.
    494
    495        Args:
    496            start_label (str): The starting vertex label.
    497            end_label (str): The vertex label to search for.
    498
    499        Returns:
    500            A vertex in the graph if found, None otherwise.
    501        """
    502        visited = set()
    503        queue = Queue()
    504
    505        visited.add(start_label)
    506        queue.enqueue(start_label)
    507
    508        while len(queue) > 0:
    509            current = queue.dequeue()
    510
    511            if current == end_label:
    512                return current
    513
    514            for v in self[current]:
    515                if v not in visited:
    516                    visited.add(v)
    517                    queue.enqueue(v)
    518
    519        return None
    520
    521    def __getitem__(self, label: str):
    522        """
    523        Args:
    524            vertex: vertex label
    525        Returns:
    526            a list of adjacent vertex labels
    527        """
    528
    529        return self._adjacents[label]
    530
    531    def is_edge(self, start_label: str, end_label: str) -> bool:
    532        """
    533        Return boolean if an edge exists
    534        Args:
    535            start_label (str): The starting vertex label.
    536            end_label (str): The ending vertex label.
    537
    538        Returns:
    539            A boolean of whether there is an edge from start to end
    540        """
    541        return end_label in self[start_label]
    542
    543    def __len__(self):
    544        """
    545        Return the number of nodes in the graph.
    546        Returns:
    547            int: The number of nodes in the graph.
    548        """
    549        return len(self._adjacents)
    550
    551    def __contains__(self, label: str) -> bool:
    552        """
    553        Args:
    554            label (str): The vertex label.
    555        Returns:
    556            A boolean indicating if the vertex is in the graph.
    557        """
    558        return label in self._adjacents
    559
    560    def edges(self) -> list:
    561        """
    562        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    563        """
    564        edges = []
    565        for start in self._adjacents.keys():
    566            for end in self.adjacents(start):
    567                if start != end:
    568                    edges.append((start, end))
    569        return edges
    570
    571    def undirected_edges(self) -> list:
    572        """
    573        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    574        """
    575        edges = []
    576        for start in self._adjacents.keys():
    577            for end in self.adjacents(start):
    578                if start != end and (end, start) not in edges:
    579                    edges.append((start, end))
    580        return edges
    581
    582class AdjacencyListWeightedGraph(AdjacencyListGraph):
    583    """
    584    A weighted adjacency list vertex implementation in Python
    585    (allows either directed or undirected representation)
    586    """
    587    def __init__(self):
    588        #: hash table of vertices in graph
    589        self._adjacents = {}
    590
    591    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    592        """
    593        Add an edge to the graph.
    594
    595        Args:
    596            start_label: The starting vertex label.
    597            end_label: The ending vertex label.
    598            weight: The edge weight.
    599            directed: Whether the edge is directed.
    600        """
    601        if start_label not in self._adjacents:
    602            self._adjacents[start_label] = {}
    603
    604        self._adjacents[start_label][end_label] = weight
    605
    606        if not directed:
    607            if end_label not in self._adjacents:
    608                self._adjacents[end_label] = {}
    609            self._adjacents[end_label][start_label] = weight
    610
    611    def delete_edge(self, start_label: str, end_label: str, directed=False):
    612        """
    613        Delete an edge in the graph.
    614
    615        Args:
    616            start_label (str): Starting vertex label.
    617            end_label (str): Ending vertex label.
    618            directed (bool): Whether the edge is directed.
    619
    620        Raises:
    621            KeyError: If the edge does exist.
    622        """
    623        if start_label not in self._adjacents:
    624            raise KeyError(f"Vertex {start_label} does not exist")
    625        if end_label not in self._adjacents[start_label]:
    626            raise KeyError(f"Vertex {end_label} does not exist")
    627        del self._adjacents[start_label][end_label]
    628        if not directed:
    629            if end_label not in self._adjacents:
    630                raise KeyError(f"Vertex {end_label} does not exist")
    631            if start_label not in self._adjacents[end_label]:
    632                raise KeyError(f"Vertex {start_label} does not exist")
    633            del self._adjacents[end_label][start_label]
    634
    635    def adjacents(self, vertex: str) -> list:
    636        """
    637        Return a list of adjacents of a given vertex
    638        Args:
    639            vertex: starting vertex label
    640        """
    641        return self._adjacents[vertex]
    642
    643    def dfs_traverse(self):
    644        """
    645        Perform depth first traversal.
    646        """
    647        return self._df_traverse_rec(self, set())
    648
    649    def _df_traverse_rec(self, vertex, visited=None):
    650        """
    651        helper depth first traversal recursive function
    652        """
    653        visited.add(vertex)
    654
    655        for v in vertex.adjacents:
    656            if v not in visited:
    657                v._df_traverse_rec(v, visited)
    658
    659    def bfs_traverse(self):
    660        """
    661        Perform breadth first traversal.
    662        """
    663        start = self
    664        visited = set()
    665        queue = Queue()
    666
    667        queue.enqueue(start)
    668
    669        while len(queue) > 0:
    670            current = queue.dequeue()
    671
    672            if current not in visited:
    673                visited.add(current)
    674
    675                for v in current.adjacents:
    676                    queue.enqueue(v)
    677
    678    def dfs(self, start_label: str, end_label: str) -> str:
    679        """
    680        Recursive depth first search.
    681
    682        Args:
    683            start_label: The starting vertex label.
    684            end_label: The vertex label to search for.
    685        Returns:
    686            vertex in the graph
    687            none if not found.
    688        """
    689        return self.dfs_rec(start_label, end_label, set())
    690
    691    def dfs_rec(self, current, end_label, visited=None):
    692        """
    693        Helper depth-first search recursive function.
    694
    695        Args:
    696            current: Current vertex
    697            end_label: Target vertex label
    698            visited: Set of visited vertices
    699
    700        Returns:
    701            vertex in the graph if found, None otherwise.
    702        """
    703        if visited is None:
    704            visited = set()
    705
    706        if current == end_label:
    707            return current
    708
    709        visited.add(current)
    710
    711        for v in self.adjacents(current):
    712            if v not in visited:
    713                result = self.dfs_rec(v, end_label, visited)
    714                if result is not None:
    715                    return result
    716
    717        return None
    718
    719
    720    def bfs(self, start_label: str, end_label: str) -> str:
    721        """
    722        Recursive breadth first search.
    723
    724        Args:
    725            end: vertex to search for
    726
    727        Returns:
    728            vertex in the graph
    729            None if not found.
    730        """
    731        visited = set()
    732        queue = Queue()
    733
    734        visited.add(start_label)
    735        queue.enqueue(start_label)
    736
    737        while not queue.is_empty():
    738            current = queue.dequeue()
    739
    740            if current == end_label:
    741                return current
    742
    743            for v in self[current]:
    744                if v not in visited:
    745                    visited.add(v)
    746                    queue.enqueue(v)
    747
    748        return None
    749
    750    def __getitem__(self, vertex: str) -> dict:
    751        """
    752        Args:
    753            vertex: vertex label
    754        Returns:
    755        Return a hashtable of adjacent vertices
    756        """
    757        if vertex not in self._adjacents:
    758            return {}
    759        return self._adjacents[vertex]
    760
    761    def __len__(self):
    762        """
    763        Return the number of nodes in the graph.
    764
    765        Returns:
    766            int: The number of nodes in the graph.
    767        """
    768        return len(self._adjacents)
    769
    770    def edges(self) -> list:
    771        """
    772        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    773        """
    774        edges = []
    775        for start in self._adjacents.keys():
    776            for end in self.adjacents(start):
    777                weight = self[start][end]
    778                if start != end:
    779                    edges.append((start, end, weight))
    780        return edges
    781
    782    def undirected_edges(self) -> list:
    783        """
    784        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    785        """
    786        edges = []
    787        for start in self._adjacents.keys():
    788            for end in self.adjacents(start):
    789                weight = self[start][end]
    790                if start != end and (end, start, weight) not in edges:
    791                    edges.append((start, end, weight))
    792        return edges
    793
    794    def is_edge(self, start_label: str, end_label: str) -> bool:
    795        """
    796        Return boolean if an edge exists
    797        Args:
    798            start_label (str): starting vertex label
    799            end_label (str): starting vertex label
    800
    801        Returns:
    802            A boolean of whether there is an edge from start to end.
    803        """
    804        return end_label in self[start_label]
    805
    806
    807
    808__pdoc__ = {
    809    'AdjacencyMatrixGraph.add_vertex': False,
    810    'AdjacencyMatrixGraph.delete_vertex': False,
    811    'AdjacencyListGraph.add_directed_edge': False,
    812}

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 __contains__(self, label: str) -> bool:
    200        """
    201        Args:
    202            label (str): The vertex label.
    203        Returns:
    204            A boolean indicating if the vertex is in the graph.
    205        """
    206        return label in self.label_index
    207
    208    def print_graph(self):
    209        """
    210        Print the contents of the graph.
    211        """
    212        print("   |", end="")
    213        for label in self.labels:
    214            print(f"{label:^3}", end=" ")
    215        print()
    216        print("----" * (len(self.array) + 1))
    217        for r, row in enumerate(self.array):
    218            label = self.labels[r]
    219            print(f"{label:^3}|", end="")
    220            for col in row:
    221                b = " T " if col else "   "
    222                print(b, end=" ")
    223            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

    208    def print_graph(self):
    209        """
    210        Print the contents of the graph.
    211        """
    212        print("   |", end="")
    213        for label in self.labels:
    214            print(f"{label:^3}", end=" ")
    215        print()
    216        print("----" * (len(self.array) + 1))
    217        for r, row in enumerate(self.array):
    218            label = self.labels[r]
    219            print(f"{label:^3}|", end="")
    220            for col in row:
    221                b = " T " if col else "   "
    222                print(b, end=" ")
    223            print()

Print the contents of the graph.

class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): View Source

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

    231    def __init__(self, labels):
    232        """
    233        Args:
    234            labels: list of labels for each vertex (string types)
    235        """
    236        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

    238    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    239        """
    240        Add an edge to the graph.
    241
    242        Args:
    243            start_label (str): The starting vertex label.
    244            end_label (str): The ending vertex label.
    245            weight: The weight of the vertex.
    246            directed: Whether the edge is directed.
    247        """
    248        a = self.label_index[start_label]
    249        b = self.label_index[end_label]
    250
    251        self.array[a][b] = weight
    252        self.array[a][a] = 0
    253        self.array[b][b] = 0
    254
    255        if not directed:
    256            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

    258    def print_graph(self):
    259        """
    260        Print the contents of the graph.
    261        """
    262        print("   |", end="")
    263        for label in self.labels:
    264            print(f"{label:>3}", end=" ")
    265        print()
    266        print("----" * (len(self.array) + 1))
    267        for r, row in enumerate(self.array):
    268            label = self.labels[r]
    269            print(f"{label:^3}|", end="")
    270            for col in row:
    271                w = f"{col:3}" if col is not None else "   "
    272                print(w, end=" ")
    273            print()

Print the contents of the graph.

def edges(self) -> list: View Source

    275    def edges(self) -> list:
    276        """
    277        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    278        """
    279        edges = []
    280        vertex_count = len(self.labels)
    281        for i in range(vertex_count):
    282            for j in range(vertex_count):
    283                weight = self.array[i][j]
    284                if weight and i != j:
    285                    edges.append((self.labels[i], self.labels[j], weight))
    286
    287        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

    289    def undirected_edges(self) -> list:
    290        """
    291        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    292        """
    293        edges = []
    294        vertex_count = len(self.labels)
    295        for i in range(vertex_count):
    296            for j in range(vertex_count):
    297                weight = self.array[i][j]
    298                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:
    299                    edges.append((self.labels[i], self.labels[j], weight))
    300
    301        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

    303    def is_edge(self, start_label: str, end_label: str) -> bool:
    304        """
    305        Return boolean if an edge exists.
    306
    307
    308        Args:
    309            start_label (str): Starting vertex label.
    310            end_label (str): Ending vertex label.
    311
    312        Returns:
    313            A boolean of whether there is an edge from start to end.
    314        """
    315        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

    317    def weightx(self, start: str, end: str) -> bool:
    318        """
    319        Return weight of an edge (is this used???)
    320        Args:
    321            start_label: starting vertex label
    322            end_label: starting vertex label
    323
    324        Returns:
    325            weight value of an edge from start to end
    326        """
    327        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

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

    349    def add_edge(self, start_label: str, end_label: str, directed=False):
    350        """
    351        Add an edge to the graph.
    352
    353        Args:
    354            start_label (str): The label of the starting vertex.
    355            end_label (str): The label of the ending vertex.
    356            directed (bool): Whether the edge is directed.
    357        """
    358        self.add_directed_edge(start_label, end_label)
    359        if not directed:
    360            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

    362    def add_directed_edge(self, start_label: str, end_label: str):
    363        """
    364        Add a directed edge to the graph.
    365
    366        Args:
    367            start_label (str): The label of the starting vertex.
    368            end_label (str): The label of the ending vertex.
    369        """
    370        if start_label not in self._adjacents:
    371            self._adjacents[start_label] = [end_label]
    372        else:
    373            if end_label not in self._adjacents[start_label]:
    374                self._adjacents[start_label].append(end_label)
    375        if end_label not in self._adjacents:
    376            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

    378    def delete_edge(self, start_label: str, end_label: str, directed=False):
    379        """
    380        Delete an edge in the graph.
    381
    382        Args:
    383            start_label (str): Starting vertex label.
    384            end_label (str): Ending vertex label.
    385            directed (bool): Whether the edge is directed.
    386        Raises:
    387            KeyError: If the edge does exist.
    388        """
    389        if start_label not in self._adjacents:
    390            raise KeyError(f"Vertex {start_label} does not exist")
    391        if end_label not in self._adjacents[start_label]:
    392            raise KeyError(f"Vertex {end_label} does not exist")
    393        self._adjacents[start_label].remove(end_label)
    394        if not directed:
    395            if end_label not in self._adjacents:
    396                raise KeyError(f"Vertex {end_label} does not exist")
    397            if start_label not in self._adjacents[end_label]:
    398                raise KeyError(f"Vertex {start_label} does not exist")
    399            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

    401    def vertices(self) -> list:
    402        """
    403        Return a list of vertex labels of the graph
    404        """
    405        return list(self._adjacents.keys())

Return a list of vertex labels of the graph

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

    407    def adjacents(self, vertex: str) -> list:
    408        """
    409        Return a list of adjacents of a given vertex
    410
    411        Args:
    412            vertex (str): The label of the vertex.
    413        """
    414        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

    416    def dfs_traverse(self, start_label: str):
    417        """
    418        Return a list of vertices through depth first traversal starting at a given vertex.
    419
    420        Args:
    421            start_label (str): The starting vertex label.
    422        Returns:
    423            A list of vertex labels.
    424        """
    425        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

    439    def bfs_traverse(self, start_label: str):
    440        """
    441        Return a list of vertices through breadth first traversal starting at a given vertex
    442        Args:
    443            start_label (str): The starting vertex label.
    444        Returns:
    445            A list of vertex labels.
    446        """
    447        visited = set()
    448        q = Queue()
    449        bflist = []
    450
    451        q.enqueue(start_label)
    452        visited.add(start_label)
    453        bflist.append(start_label)
    454
    455        while len(q) > 0:
    456            current = q.dequeue()
    457
    458            for v in self[current]:
    459                if v not in visited:
    460                    visited.add(v)
    461                    q.enqueue(v)
    462                    bflist.append(v)
    463        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

    465    def dfs(self, start_label:str, end_label:str) -> str:
    466        """
    467        Recursive depth first search.
    468
    469        Args:
    470            start_label (str): The starting vertex label.
    471            end_label (str): The vertex label to search for.
    472
    473        Returns:
    474            A vertex in the graph if found, None otherwise.
    475        """
    476        def dfs_rec(current: str, end: str, visited):
    477            if current == end:
    478                return current
    479
    480            visited.add(current)
    481
    482            for v in self.adjacents(current):
    483                if v not in visited:
    484                    result = dfs_rec(v, end, visited)
    485                    if result is not None:
    486                        return result
    487
    488            return None
    489
    490        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

    492    def bfs(self, start_label: str, end_label: str) -> str:
    493        """
    494        Breadth first search.
    495
    496        Args:
    497            start_label (str): The starting vertex label.
    498            end_label (str): The vertex label to search for.
    499
    500        Returns:
    501            A vertex in the graph if found, None otherwise.
    502        """
    503        visited = set()
    504        queue = Queue()
    505
    506        visited.add(start_label)
    507        queue.enqueue(start_label)
    508
    509        while len(queue) > 0:
    510            current = queue.dequeue()
    511
    512            if current == end_label:
    513                return current
    514
    515            for v in self[current]:
    516                if v not in visited:
    517                    visited.add(v)
    518                    queue.enqueue(v)
    519
    520        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

    532    def is_edge(self, start_label: str, end_label: str) -> bool:
    533        """
    534        Return boolean if an edge exists
    535        Args:
    536            start_label (str): The starting vertex label.
    537            end_label (str): The ending vertex label.
    538
    539        Returns:
    540            A boolean of whether there is an edge from start to end
    541        """
    542        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

    561    def edges(self) -> list:
    562        """
    563        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    564        """
    565        edges = []
    566        for start in self._adjacents.keys():
    567            for end in self.adjacents(start):
    568                if start != end:
    569                    edges.append((start, end))
    570        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

    572    def undirected_edges(self) -> list:
    573        """
    574        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    575        """
    576        edges = []
    577        for start in self._adjacents.keys():
    578            for end in self.adjacents(start):
    579                if start != end and (end, start) not in edges:
    580                    edges.append((start, end))
    581        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

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

    592    def add_edge(self, start_label: str, end_label: str, weight, directed=False):
    593        """
    594        Add an edge to the graph.
    595
    596        Args:
    597            start_label: The starting vertex label.
    598            end_label: The ending vertex label.
    599            weight: The edge weight.
    600            directed: Whether the edge is directed.
    601        """
    602        if start_label not in self._adjacents:
    603            self._adjacents[start_label] = {}
    604
    605        self._adjacents[start_label][end_label] = weight
    606
    607        if not directed:
    608            if end_label not in self._adjacents:
    609                self._adjacents[end_label] = {}
    610            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

    612    def delete_edge(self, start_label: str, end_label: str, directed=False):
    613        """
    614        Delete an edge in the graph.
    615
    616        Args:
    617            start_label (str): Starting vertex label.
    618            end_label (str): Ending vertex label.
    619            directed (bool): Whether the edge is directed.
    620
    621        Raises:
    622            KeyError: If the edge does exist.
    623        """
    624        if start_label not in self._adjacents:
    625            raise KeyError(f"Vertex {start_label} does not exist")
    626        if end_label not in self._adjacents[start_label]:
    627            raise KeyError(f"Vertex {end_label} does not exist")
    628        del self._adjacents[start_label][end_label]
    629        if not directed:
    630            if end_label not in self._adjacents:
    631                raise KeyError(f"Vertex {end_label} does not exist")
    632            if start_label not in self._adjacents[end_label]:
    633                raise KeyError(f"Vertex {start_label} does not exist")
    634            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

    636    def adjacents(self, vertex: str) -> list:
    637        """
    638        Return a list of adjacents of a given vertex
    639        Args:
    640            vertex: starting vertex label
    641        """
    642        return self._adjacents[vertex]

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

def dfs_traverse(self): View Source

    644    def dfs_traverse(self):
    645        """
    646        Perform depth first traversal.
    647        """
    648        return self._df_traverse_rec(self, set())

Perform depth first traversal.

def bfs_traverse(self): View Source

    660    def bfs_traverse(self):
    661        """
    662        Perform breadth first traversal.
    663        """
    664        start = self
    665        visited = set()
    666        queue = Queue()
    667
    668        queue.enqueue(start)
    669
    670        while len(queue) > 0:
    671            current = queue.dequeue()
    672
    673            if current not in visited:
    674                visited.add(current)
    675
    676                for v in current.adjacents:
    677                    queue.enqueue(v)

Perform breadth first traversal.

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

    679    def dfs(self, start_label: str, end_label: str) -> str:
    680        """
    681        Recursive depth first search.
    682
    683        Args:
    684            start_label: The starting vertex label.
    685            end_label: The vertex label to search for.
    686        Returns:
    687            vertex in the graph
    688            none if not found.
    689        """
    690        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

    692    def dfs_rec(self, current, end_label, visited=None):
    693        """
    694        Helper depth-first search recursive function.
    695
    696        Args:
    697            current: Current vertex
    698            end_label: Target vertex label
    699            visited: Set of visited vertices
    700
    701        Returns:
    702            vertex in the graph if found, None otherwise.
    703        """
    704        if visited is None:
    705            visited = set()
    706
    707        if current == end_label:
    708            return current
    709
    710        visited.add(current)
    711
    712        for v in self.adjacents(current):
    713            if v not in visited:
    714                result = self.dfs_rec(v, end_label, visited)
    715                if result is not None:
    716                    return result
    717
    718        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

    721    def bfs(self, start_label: str, end_label: str) -> str:
    722        """
    723        Recursive breadth first search.
    724
    725        Args:
    726            end: vertex to search for
    727
    728        Returns:
    729            vertex in the graph
    730            None if not found.
    731        """
    732        visited = set()
    733        queue = Queue()
    734
    735        visited.add(start_label)
    736        queue.enqueue(start_label)
    737
    738        while not queue.is_empty():
    739            current = queue.dequeue()
    740
    741            if current == end_label:
    742                return current
    743
    744            for v in self[current]:
    745                if v not in visited:
    746                    visited.add(v)
    747                    queue.enqueue(v)
    748
    749        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

    771    def edges(self) -> list:
    772        """
    773        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    774        """
    775        edges = []
    776        for start in self._adjacents.keys():
    777            for end in self.adjacents(start):
    778                weight = self[start][end]
    779                if start != end:
    780                    edges.append((start, end, weight))
    781        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

    783    def undirected_edges(self) -> list:
    784        """
    785        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    786        """
    787        edges = []
    788        for start in self._adjacents.keys():
    789            for end in self.adjacents(start):
    790                weight = self[start][end]
    791                if start != end and (end, start, weight) not in edges:
    792                    edges.append((start, end, weight))
    793        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

    795    def is_edge(self, start_label: str, end_label: str) -> bool:
    796        """
    797        Return boolean if an edge exists
    798        Args:
    799            start_label (str): starting vertex label
    800            end_label (str): starting vertex label
    801
    802        Returns:
    803            A boolean of whether there is an edge from start to end.
    804        """
    805        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