Module dsa.graph

Module containing graph classes.

Expand source code
""" Module containing graph classes. """

from dsa.queue import Queue

class AdjacencyMatrixGraph:
    """ 
    An unweighted adjacency matrix graph implementation.
    
    This class allows either directed or undirected representation of a graph.
    Vertex labels are string types.
    """
    def __init__(self, labels: list[str]):
        """ 
        Initialize the graph with a list of vertex labels.

        Args:
            labels (list[str]): List of labels for each vertex.
        """
        self.labels = labels
        self.label_index = { label: index for index, label  in enumerate(labels) }

        node_count = len(self.labels)
        self.array = [[None for i in range(node_count)] for j in range(node_count)]

    def add_edge(self, a_label: str, b_label: str):
        """ 
        Add an undirected edge between two vertices.
        
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_vertex(a_label, b_label)
        
    def add_adjacent_vertex(self, a_label: str, b_label: str):
        """ 
        Add an undirected edge between two vertices.
        
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]
        self.array[a][b] = True
        self.array[a][a] = True

        self.array[b][a] = True
        self.array[b][b] = True

    def add_directed_edge(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_adjacent_directed_vertex())

        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_directed_vertex(a_label, b_label)

    def add_directed_adjacent_vertex(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_adjacent_directed_vertex()  and add_directed_edge())
 
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_directed_vertex(a_label, b_label)
        
    def add_adjacent_directed_vertex(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_directed_edge())
 
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]
        self.array[a][b] = True
        self.array[a][a] = True
        self.array[b][b] = True

    def df_traverse(self, start_label: str):
        """ 
        Perform depth first traversal in an adjacency matrix
 
        Args:
            start_label (str): Starting vertex label.
        
        Returns:
            Array with depth first order traversal.
        """
        return self._df_rec_traverse(start_label, set(), [])
        
    def _df_rec_traverse(self, start_label: str, visited, dfs):
        """ 
        Helper method for depth first recursive traversal
        """
        start_index = self.label_index[start_label]
        visited.add(start_label)
        dfs.append(self.labels[start_index])

        for adj_index, is_connected in enumerate(self.array[start_index]):
            adj_label = self.labels[adj_index]
            if is_connected and adj_label not in visited:
                self._df_rec_traverse(adj_label, visited, dfs)

        return dfs

    def bf_traverse(self, start_label: str):
        """ 
        Perform breadth first traversal in an adjacency matrix.
 
        Args:
            start_label (str): Starting vertex label.
        
        Returns:
            Array with breadth first order traversal.
        """
        bfs = []
        q = Queue()
        visited = set()

        start_index = self.label_index[start_label]
        q.enqueue(start_index)
        bfs.append(self.labels[start_index])
        while not q.is_empty():
            current = q.dequeue()
            for i in range(len(self.array)):
                if start_index != i and self.array[current][i] and (i not in visited):
                    visited.add(i)
                    q.enqueue(i)
                    bfs.append(self.labels[i])
        return bfs
    
    def vertices(self) -> list:
        """"
        Return a list of vertex labels of the graph
        """
        return self.labels
    
    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                if self.array[i][j] and i != j:  
                    edges.append((self.labels[i], self.labels[j]))
    
        return edges

    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:  
                    edges.append((self.labels[i], self.labels[j]))
    
        return edges

    def is_edge(self, start: str, end: str) -> bool:
        """ 
        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.
        """
        start_index = self.label_index[start]
        end_index = self.label_index[end]

        return self.array[start_index][end_index]

    def __getitem__(self, vertex: str) -> list:
        """ 
        Args:
            vertex (str): The vertex label.
        Returns:
            A list of adjacent vertex labels.
        """
        index = self.label_index[vertex]
        return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]]
    
    def print_graph(self):
        """ 
        Print the contents of the graph.
        """
        print("   |", end="")
        for label in self.labels:
            print(f"{label:^3}", end=" ")
        print()
        print("----" * (len(self.array) + 1))
        for r, row in enumerate(self.array):
            label = self.labels[r]
            print(f"{label:^3}|", end="")
            for col in row:
                b = " T " if col else "   "
                print(b, end=" ")
            print()
            
class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph):
    """ 
    A weighted adjacency matrix graph implementation
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self, labels):
        """ 
        Args:
            labels: list of labels for each vertex (string types)
        """
        super().__init__(labels)

    def add_edge(self, a_label: str, b_label: str, weight):
        """ 
        Add an undirected edge between one vertex to another (same as add_edge())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_vertex(a_label, b_label, weight)

    def add_adjacent_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add an undirected edge between one vertex to another (same as add_edge())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]

        self.array[a][b] = weight
        self.array[a][a] = 0

        self.array[b][a] = weight
        self.array[b][b] = 0

    def add_directed_edge(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_adjacent_directed_vertex(), add_directed_adjacent_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_directed_vertex(a_label, b_label, weight)

    def add_directed_adjacent_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_adjacent_directed_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_directed_vertex(a_label, b_label, weight)

    def add_adjacent_directed_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_directed_adjacent_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]

        self.array[a][b] = weight
        self.array[a][a] = 0
        self.array[b][b] = 0
        
    def print_graph(self):
        """ 
        Print the contents of the graph.
        """
        print("   |", end="")
        for label in self.labels:
            print(f"{label:>3}", end=" ")
        print()
        print("----" * (len(self.array) + 1))
        for r, row in enumerate(self.array):
            label = self.labels[r]
            print(f"{label:^3}|", end="")
            for col in row:
                w = f"{col:3}" if col is not None else "   "
                print(w, end=" ")
            print()

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                weight = self.array[i][j]
                if weight and i != j:  
                    edges.append((self.labels[i], self.labels[j], weight))
    
        return edges
    
    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                weight = self.array[i][j]
                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:  
                    edges.append((self.labels[i], self.labels[j], weight))
    
        return edges

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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.
        """
        return super().is_edge(start_label, end_label) is not None
    
    def weightx(self, start: str, end: str) -> bool:
        """ 
        Return weight of an edge
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            weight value of an edge from start to end
        """
        return super().is_edge(start, end)

    def __getitem__(self, vertex: str) -> dict:
        """ 
        Args:
            vertex: vertex label
        Returns:
            a dictionary of adjacent vertex labels and weights
        """
        index = self.label_index[vertex]
        return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}

class AdjacencyListGraph:
    """ 
    A unweighted adjacency list vertex implementation
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self):
        #: hash table of vertices in graph
        self._adjacents = {}
        
    def add_adjacent_vertex(self, start_label: str, end_label: str):
        """ 
        Add an undirected vertex to the adjacency list (same as add_edge()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_directed_adjacent_vertex(start_label, end_label)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = [start_label]
        else:
            if start_label not in self._adjacents[end_label]:
                self._adjacents[end_label].append(start_label)
        
    def add_edge(self, start_label: str, end_label: str):
        """ 
        Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_adjacent_vertex(start_label, end_label)

    def add_directed_edge(self, start_label: str, end_label: str):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_directed_adjacent_vertex(start_label, end_label)
        
    def add_directed_adjacent_vertex(self, start_label: str, end_label: str):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_edge()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        if start_label not in self._adjacents:
            self._adjacents[start_label] = [end_label]
        else:
            if end_label not in self._adjacents[start_label]:
                self._adjacents[start_label].append(end_label)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = []
      
    def vertices(self) -> list:
        """"
        Return a list of vertex labels of the graph
        """
        return list(self._adjacents.keys())
  
    def adjacents(self, vertex: str) -> list:
        """
        Return a list of adjacents of a given vertex

        Args:
            vertex (str): The label of the vertex.
        """
        return self._adjacents[vertex]

    def df_traverse(self, start_label: str):
        """
        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.
        """
        return self._df_rec_traverse(start_label, set(), [])

    def _df_rec_traverse(self, start_label: str, visited, dflist):
        """
        Helper depth first traversal recursive function.
        """
        visited.add(start_label)
        dflist.append(start_label)
        
        for v in self[start_label]:
            if v not in visited:
                self._df_rec_traverse(v, visited, dflist)
        return dflist
    
    def bf_traverse(self, start_label: str):
        """
        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.
        """
        visited = set()
        q = Queue()
        bflist = []

        q.enqueue(start_label)
        visited.add(start_label)
        bflist.append(start_label)

        while len(q) > 0:
            current = q.dequeue()

            for v in self[current]:
                if v not in visited:               
                    visited.add(v)
                    q.enqueue(v)
                    bflist.append(v)
        return bflist
        
    def dfs(self, start_label:str, end_label:str) -> str:
        """ 
        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 dfs_rec(current: str, end: str, visited):
            if current == end:
                return current

            visited.add(current)

            for v in self.adjacents(current):
                if v not in visited:
                    result = dfs_rec(v, end, visited)
                    if result is not None:
                        return result
            
            return None
        
        return dfs_rec(start_label, end_label, set())
    
    def bfs(self, start_label: str, end_label: str) -> str:
        """ 
        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.
        """
        visited = set()
        queue = Queue()
        
        visited.add(start_label)
        queue.enqueue(start_label)

        while len(queue) > 0:
            current = queue.dequeue()
            
            if current == end_label:
                return current
            
            for v in self[current]:
                if v not in visited:               
                    visited.add(v)
                    queue.enqueue(v)
        
        return None
    
    def __getitem__(self, label: str):
        """ 
        Args:
            vertex: vertex label
        Returns:
            a list of adjacent vertex labels
        """

        return self._adjacents[label]

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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
        """
        return end_label in self[start_label]

    def __len__(self):
        return len(self._adjacents)

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                if start != end:  
                    edges.append((start, end))
        return edges

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

class AdjacencyListWeightedGraph(AdjacencyListGraph):
    """ 
    A weighted adjacency list vertex implementation in Python
    (allows either directed or undirected representation)
    """
    def __init__(self):
        #: hash table of vertices in graph
        self._adjacents = {}
        
    def add_adjacent_vertex(self, start_label: str, end_label: str, weight):
        """ 
        Add an undirected vertex to the adjacency list (same as add_edge()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_directed_adjacent_vertex(start_label, end_label, weight)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = {}
        self._adjacents[end_label][start_label] = weight
        
    def add_edge(self, start_label: str, end_label: str, weight):
        """ 
        Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_adjacent_vertex(start_label, end_label, weight)

    def add_directed_edge(self, start_label: str, end_label: str, weight):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_directed_adjacent_vertex(start_label, end_label, weight)
        
    def add_directed_adjacent_vertex(self, start_label: str, end_label: str, weight):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_edge()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        if start_label not in self._adjacents:
            self._adjacents[start_label] = {}
            
        self._adjacents[start_label][end_label] = weight
        if end_label not in self._adjacents:
            self._adjacents[end_label] = {}

    def adjacents(self, vertex: str) -> list:
        """
        Return a list of adjacents of a given vertex
        Args:
            vertex: starting vertex label 
        """
        return self._adjacents[vertex]

    def df_traverse(self):
        """
        Perform depth first traversal.
        """
        return self._df_traverse_rec(self, set())

    def _df_traverse_rec(self, vertex, visited=None):
        """
        helper depth first traversal recursive function
        """
        visited.add(vertex)
        
        for v in vertex.adjacents:
            if v not in visited:
                v._df_traverse_rec(v, visited)
            
    def bf_traverse(self):
        """
        Perform breadth first traversal.
        """
        start = self
        visited = set()
        queue = Queue()
        
        queue.enqueue(start)

        while len(queue) > 0:
            current = queue.dequeue()
            
            if current not in visited:               
                visited.add(current)
        
                for v in current.adjacents:
                    queue.enqueue(v)
        
    def dfs(self, start_label: str, end_label: str) -> str:
        """ 
        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.
        """
        return self.dfs_rec(start_label, end_label, set())
        
    def dfs_rec(self, current, end_label, visited=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.
        """
        if visited is None:
            visited = set()
        
        if current == end_label:
            return current

        visited.add(current)
        
        for v in self.adjacents(current):
            if v not in visited:
                result = self.dfs_rec(v, end_label, visited)
                if result is not None:
                    return result
        
        return None


    def bfs(self, start_label: str, end_label: str) -> str:
        """ 
        Recursive breadth first search.

        Args:
            end: vertex to search for

        Returns:
            vertex in the graph
            None if not found.
        """
        visited = set()
        queue = Queue()
        
        visited.add(start_label)
        queue.enqueue(start_label)

        while not queue.is_empty():
            current = queue.dequeue()
            
            if current == end_label:
                return current
            
            for v in self[current]:
                if v not in visited:
                    visited.add(v)
                    queue.enqueue(v)
        
        return None

    def __getitem__(self, vertex: str):
        """
        Args:
            vertex: vertex label
        Returns:
        Return a list of adjacent vertices
        """
        return self._adjacents[vertex]

    def __len__(self):
        return len(self._adjacents)

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                weight = self[start][end]
                if start != end:  
                    edges.append((start, end, weight))
        return edges

    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                weight = self[start][end]
                if start != end and (end, start, weight) not in edges:  
                    edges.append((start, end, weight))
        return edges

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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.
        """
        return end_label in self[start_label]

Classes

class AdjacencyListGraph

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

Expand source code
class AdjacencyListGraph:
    """ 
    A unweighted adjacency list vertex implementation
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self):
        #: hash table of vertices in graph
        self._adjacents = {}
        
    def add_adjacent_vertex(self, start_label: str, end_label: str):
        """ 
        Add an undirected vertex to the adjacency list (same as add_edge()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_directed_adjacent_vertex(start_label, end_label)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = [start_label]
        else:
            if start_label not in self._adjacents[end_label]:
                self._adjacents[end_label].append(start_label)
        
    def add_edge(self, start_label: str, end_label: str):
        """ 
        Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_adjacent_vertex(start_label, end_label)

    def add_directed_edge(self, start_label: str, end_label: str):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        self.add_directed_adjacent_vertex(start_label, end_label)
        
    def add_directed_adjacent_vertex(self, start_label: str, end_label: str):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_edge()).

        Args:
            start_label (str): The label of the starting vertex.
            end_label (str): The label of the ending vertex.
        """
        if start_label not in self._adjacents:
            self._adjacents[start_label] = [end_label]
        else:
            if end_label not in self._adjacents[start_label]:
                self._adjacents[start_label].append(end_label)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = []
      
    def vertices(self) -> list:
        """"
        Return a list of vertex labels of the graph
        """
        return list(self._adjacents.keys())
  
    def adjacents(self, vertex: str) -> list:
        """
        Return a list of adjacents of a given vertex

        Args:
            vertex (str): The label of the vertex.
        """
        return self._adjacents[vertex]

    def df_traverse(self, start_label: str):
        """
        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.
        """
        return self._df_rec_traverse(start_label, set(), [])

    def _df_rec_traverse(self, start_label: str, visited, dflist):
        """
        Helper depth first traversal recursive function.
        """
        visited.add(start_label)
        dflist.append(start_label)
        
        for v in self[start_label]:
            if v not in visited:
                self._df_rec_traverse(v, visited, dflist)
        return dflist
    
    def bf_traverse(self, start_label: str):
        """
        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.
        """
        visited = set()
        q = Queue()
        bflist = []

        q.enqueue(start_label)
        visited.add(start_label)
        bflist.append(start_label)

        while len(q) > 0:
            current = q.dequeue()

            for v in self[current]:
                if v not in visited:               
                    visited.add(v)
                    q.enqueue(v)
                    bflist.append(v)
        return bflist
        
    def dfs(self, start_label:str, end_label:str) -> str:
        """ 
        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 dfs_rec(current: str, end: str, visited):
            if current == end:
                return current

            visited.add(current)

            for v in self.adjacents(current):
                if v not in visited:
                    result = dfs_rec(v, end, visited)
                    if result is not None:
                        return result
            
            return None
        
        return dfs_rec(start_label, end_label, set())
    
    def bfs(self, start_label: str, end_label: str) -> str:
        """ 
        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.
        """
        visited = set()
        queue = Queue()
        
        visited.add(start_label)
        queue.enqueue(start_label)

        while len(queue) > 0:
            current = queue.dequeue()
            
            if current == end_label:
                return current
            
            for v in self[current]:
                if v not in visited:               
                    visited.add(v)
                    queue.enqueue(v)
        
        return None
    
    def __getitem__(self, label: str):
        """ 
        Args:
            vertex: vertex label
        Returns:
            a list of adjacent vertex labels
        """

        return self._adjacents[label]

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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
        """
        return end_label in self[start_label]

    def __len__(self):
        return len(self._adjacents)

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                if start != end:  
                    edges.append((start, end))
        return edges

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

Subclasses

Methods

def add_adjacent_vertex(self, start_label: str, end_label: str)

Add an undirected vertex to the adjacency list (same as add_edge()).

Args

start_label : str
The label of the starting vertex.
end_label : str
The label of the ending vertex.
Expand source code
def add_adjacent_vertex(self, start_label: str, end_label: str):
    """ 
    Add an undirected vertex to the adjacency list (same as add_edge()).

    Args:
        start_label (str): The label of the starting vertex.
        end_label (str): The label of the ending vertex.
    """
    self.add_directed_adjacent_vertex(start_label, end_label)
    if end_label not in self._adjacents:
        self._adjacents[end_label] = [start_label]
    else:
        if start_label not in self._adjacents[end_label]:
            self._adjacents[end_label].append(start_label)
def add_directed_adjacent_vertex(self, start_label: str, end_label: str)

Add a directed vertex to the adjacency list (same as add_directed_edge()).

Args

start_label : str
The label of the starting vertex.
end_label : str
The label of the ending vertex.
Expand source code
def add_directed_adjacent_vertex(self, start_label: str, end_label: str):
    """ 
    Add a directed vertex to the adjacency list (same as add_directed_edge()).

    Args:
        start_label (str): The label of the starting vertex.
        end_label (str): The label of the ending vertex.
    """
    if start_label not in self._adjacents:
        self._adjacents[start_label] = [end_label]
    else:
        if end_label not in self._adjacents[start_label]:
            self._adjacents[start_label].append(end_label)
    if end_label not in self._adjacents:
        self._adjacents[end_label] = []
def add_directed_edge(self, start_label: str, end_label: str)

Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

Args

start_label : str
The label of the starting vertex.
end_label : str
The label of the ending vertex.
Expand source code
def add_directed_edge(self, start_label: str, end_label: str):
    """ 
    Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

    Args:
        start_label (str): The label of the starting vertex.
        end_label (str): The label of the ending vertex.
    """
    self.add_directed_adjacent_vertex(start_label, end_label)
def add_edge(self, start_label: str, end_label: str)

Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

Args

start_label : str
The label of the starting vertex.
end_label : str
The label of the ending vertex.
Expand source code
def add_edge(self, start_label: str, end_label: str):
    """ 
    Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

    Args:
        start_label (str): The label of the starting vertex.
        end_label (str): The label of the ending vertex.
    """
    self.add_adjacent_vertex(start_label, end_label)
def adjacents(self, vertex: str) ‑> list

Return a list of adjacents of a given vertex

Args

vertex : str
The label of the vertex.
Expand source code
def adjacents(self, vertex: str) -> list:
    """
    Return a list of adjacents of a given vertex

    Args:
        vertex (str): The label of the vertex.
    """
    return self._adjacents[vertex]
def bf_traverse(self, start_label: str)

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.

Expand source code
def bf_traverse(self, start_label: str):
    """
    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.
    """
    visited = set()
    q = Queue()
    bflist = []

    q.enqueue(start_label)
    visited.add(start_label)
    bflist.append(start_label)

    while len(q) > 0:
        current = q.dequeue()

        for v in self[current]:
            if v not in visited:               
                visited.add(v)
                q.enqueue(v)
                bflist.append(v)
    return bflist
def bfs(self, start_label: str, end_label: str) ‑> str

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.

Expand source code
def bfs(self, start_label: str, end_label: str) -> str:
    """ 
    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.
    """
    visited = set()
    queue = Queue()
    
    visited.add(start_label)
    queue.enqueue(start_label)

    while len(queue) > 0:
        current = queue.dequeue()
        
        if current == end_label:
            return current
        
        for v in self[current]:
            if v not in visited:               
                visited.add(v)
                queue.enqueue(v)
    
    return None
def df_traverse(self, start_label: str)

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.

Expand source code
def df_traverse(self, start_label: str):
    """
    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.
    """
    return self._df_rec_traverse(start_label, set(), [])
def dfs(self, start_label: str, end_label: str) ‑> str

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.

Expand source code
def dfs(self, start_label:str, end_label:str) -> str:
    """ 
    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 dfs_rec(current: str, end: str, visited):
        if current == end:
            return current

        visited.add(current)

        for v in self.adjacents(current):
            if v not in visited:
                result = dfs_rec(v, end, visited)
                if result is not None:
                    return result
        
        return None
    
    return dfs_rec(start_label, end_label, set())
def edges(self) ‑> list

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

Expand source code
def edges(self) -> list:
    """ 
    Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    """
    edges = []
    for start in self._adjacents.keys():
        for end in self.adjacents(start):
            if start != end:  
                edges.append((start, end))
    return edges
def is_edge(self, start_label: str, end_label: str) ‑> bool

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

Expand source code
def is_edge(self, start_label: str, end_label: str) -> bool:
    """ 
    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
    """
    return end_label in self[start_label]
def undirected_edges(self) ‑> list

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

Expand source code
def undirected_edges(self) -> list:
    """ 
    Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    """
    edges = []
    for start in self._adjacents.keys():
        for end in self.adjacents(start):
            if start != end and (end, start) not in edges:  
                edges.append((start, end))
    return edges
def vertices(self) ‑> list

" Return a list of vertex labels of the graph

Expand source code
def vertices(self) -> list:
    """"
    Return a list of vertex labels of the graph
    """
    return list(self._adjacents.keys())
class AdjacencyListWeightedGraph

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

Expand source code
class AdjacencyListWeightedGraph(AdjacencyListGraph):
    """ 
    A weighted adjacency list vertex implementation in Python
    (allows either directed or undirected representation)
    """
    def __init__(self):
        #: hash table of vertices in graph
        self._adjacents = {}
        
    def add_adjacent_vertex(self, start_label: str, end_label: str, weight):
        """ 
        Add an undirected vertex to the adjacency list (same as add_edge()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_directed_adjacent_vertex(start_label, end_label, weight)
        if end_label not in self._adjacents:
            self._adjacents[end_label] = {}
        self._adjacents[end_label][start_label] = weight
        
    def add_edge(self, start_label: str, end_label: str, weight):
        """ 
        Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_adjacent_vertex(start_label, end_label, weight)

    def add_directed_edge(self, start_label: str, end_label: str, weight):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        self.add_directed_adjacent_vertex(start_label, end_label, weight)
        
    def add_directed_adjacent_vertex(self, start_label: str, end_label: str, weight):
        """ 
        Add a directed vertex to the adjacency list (same as add_directed_edge()).

        Args:
            start_label: The starting vertex label. 
            end_label: The ending vertex label. 
            weight: The edge weight.
        """
        if start_label not in self._adjacents:
            self._adjacents[start_label] = {}
            
        self._adjacents[start_label][end_label] = weight
        if end_label not in self._adjacents:
            self._adjacents[end_label] = {}

    def adjacents(self, vertex: str) -> list:
        """
        Return a list of adjacents of a given vertex
        Args:
            vertex: starting vertex label 
        """
        return self._adjacents[vertex]

    def df_traverse(self):
        """
        Perform depth first traversal.
        """
        return self._df_traverse_rec(self, set())

    def _df_traverse_rec(self, vertex, visited=None):
        """
        helper depth first traversal recursive function
        """
        visited.add(vertex)
        
        for v in vertex.adjacents:
            if v not in visited:
                v._df_traverse_rec(v, visited)
            
    def bf_traverse(self):
        """
        Perform breadth first traversal.
        """
        start = self
        visited = set()
        queue = Queue()
        
        queue.enqueue(start)

        while len(queue) > 0:
            current = queue.dequeue()
            
            if current not in visited:               
                visited.add(current)
        
                for v in current.adjacents:
                    queue.enqueue(v)
        
    def dfs(self, start_label: str, end_label: str) -> str:
        """ 
        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.
        """
        return self.dfs_rec(start_label, end_label, set())
        
    def dfs_rec(self, current, end_label, visited=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.
        """
        if visited is None:
            visited = set()
        
        if current == end_label:
            return current

        visited.add(current)
        
        for v in self.adjacents(current):
            if v not in visited:
                result = self.dfs_rec(v, end_label, visited)
                if result is not None:
                    return result
        
        return None


    def bfs(self, start_label: str, end_label: str) -> str:
        """ 
        Recursive breadth first search.

        Args:
            end: vertex to search for

        Returns:
            vertex in the graph
            None if not found.
        """
        visited = set()
        queue = Queue()
        
        visited.add(start_label)
        queue.enqueue(start_label)

        while not queue.is_empty():
            current = queue.dequeue()
            
            if current == end_label:
                return current
            
            for v in self[current]:
                if v not in visited:
                    visited.add(v)
                    queue.enqueue(v)
        
        return None

    def __getitem__(self, vertex: str):
        """
        Args:
            vertex: vertex label
        Returns:
        Return a list of adjacent vertices
        """
        return self._adjacents[vertex]

    def __len__(self):
        return len(self._adjacents)

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                weight = self[start][end]
                if start != end:  
                    edges.append((start, end, weight))
        return edges

    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
        """
        edges = []
        for start in self._adjacents.keys():
            for end in self.adjacents(start):
                weight = self[start][end]
                if start != end and (end, start, weight) not in edges:  
                    edges.append((start, end, weight))
        return edges

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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.
        """
        return end_label in self[start_label]

Ancestors

Methods

def add_adjacent_vertex(self, start_label: str, end_label: str, weight)

Add an undirected vertex to the adjacency list (same as add_edge()).

Args

start_label
The starting vertex label.
end_label
The ending vertex label.
weight
The edge weight.
Expand source code
def add_adjacent_vertex(self, start_label: str, end_label: str, weight):
    """ 
    Add an undirected vertex to the adjacency list (same as add_edge()).

    Args:
        start_label: The starting vertex label. 
        end_label: The ending vertex label. 
        weight: The edge weight.
    """
    self.add_directed_adjacent_vertex(start_label, end_label, weight)
    if end_label not in self._adjacents:
        self._adjacents[end_label] = {}
    self._adjacents[end_label][start_label] = weight
def add_directed_adjacent_vertex(self, start_label: str, end_label: str, weight)

Add a directed vertex to the adjacency list (same as add_directed_edge()).

Args

start_label
The starting vertex label.
end_label
The ending vertex label.
weight
The edge weight.
Expand source code
def add_directed_adjacent_vertex(self, start_label: str, end_label: str, weight):
    """ 
    Add a directed vertex to the adjacency list (same as add_directed_edge()).

    Args:
        start_label: The starting vertex label. 
        end_label: The ending vertex label. 
        weight: The edge weight.
    """
    if start_label not in self._adjacents:
        self._adjacents[start_label] = {}
        
    self._adjacents[start_label][end_label] = weight
    if end_label not in self._adjacents:
        self._adjacents[end_label] = {}
def add_directed_edge(self, start_label: str, end_label: str, weight)

Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

Args

start_label
The starting vertex label.
end_label
The ending vertex label.
weight
The edge weight.
Expand source code
def add_directed_edge(self, start_label: str, end_label: str, weight):
    """ 
    Add a directed vertex to the adjacency list (same as add_directed_adjacent_vertex()).

    Args:
        start_label: The starting vertex label. 
        end_label: The ending vertex label. 
        weight: The edge weight.
    """
    self.add_directed_adjacent_vertex(start_label, end_label, weight)
def add_edge(self, start_label: str, end_label: str, weight)

Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

Args

start_label
The starting vertex label.
end_label
The ending vertex label.
weight
The edge weight.
Expand source code
def add_edge(self, start_label: str, end_label: str, weight):
    """ 
    Add an undirected vertex to the adjacency list (same as add_adjacent_vertex()).

    Args:
        start_label: The starting vertex label. 
        end_label: The ending vertex label. 
        weight: The edge weight.
    """
    self.add_adjacent_vertex(start_label, end_label, weight)
def adjacents(self, vertex: str) ‑> list

Return a list of adjacents of a given vertex

Args

vertex
starting vertex label
Expand source code
def adjacents(self, vertex: str) -> list:
    """
    Return a list of adjacents of a given vertex
    Args:
        vertex: starting vertex label 
    """
    return self._adjacents[vertex]
def bf_traverse(self)

Perform breadth first traversal.

Expand source code
def bf_traverse(self):
    """
    Perform breadth first traversal.
    """
    start = self
    visited = set()
    queue = Queue()
    
    queue.enqueue(start)

    while len(queue) > 0:
        current = queue.dequeue()
        
        if current not in visited:               
            visited.add(current)
    
            for v in current.adjacents:
                queue.enqueue(v)
def bfs(self, start_label: str, end_label: str) ‑> str

Recursive breadth first search.

Args

end
vertex to search for

Returns

vertex in the graph None if not found.

Expand source code
def bfs(self, start_label: str, end_label: str) -> str:
    """ 
    Recursive breadth first search.

    Args:
        end: vertex to search for

    Returns:
        vertex in the graph
        None if not found.
    """
    visited = set()
    queue = Queue()
    
    visited.add(start_label)
    queue.enqueue(start_label)

    while not queue.is_empty():
        current = queue.dequeue()
        
        if current == end_label:
            return current
        
        for v in self[current]:
            if v not in visited:
                visited.add(v)
                queue.enqueue(v)
    
    return None
def df_traverse(self)

Perform depth first traversal.

Expand source code
def df_traverse(self):
    """
    Perform depth first traversal.
    """
    return self._df_traverse_rec(self, set())
def dfs(self, start_label: str, end_label: str) ‑> str

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.

Expand source code
def dfs(self, start_label: str, end_label: str) -> str:
    """ 
    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.
    """
    return self.dfs_rec(start_label, end_label, set())
def dfs_rec(self, current, end_label, visited=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.

Expand source code
def dfs_rec(self, current, end_label, visited=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.
    """
    if visited is None:
        visited = set()
    
    if current == end_label:
        return current

    visited.add(current)
    
    for v in self.adjacents(current):
        if v not in visited:
            result = self.dfs_rec(v, end_label, visited)
            if result is not None:
                return result
    
    return None
def edges(self) ‑> list

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

Expand source code
def edges(self) -> list:
    """ 
    Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
    """
    edges = []
    for start in self._adjacents.keys():
        for end in self.adjacents(start):
            weight = self[start][end]
            if start != end:  
                edges.append((start, end, weight))
    return edges
def is_edge(self, start_label: str, end_label: str) ‑> bool

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.

Expand source code
def is_edge(self, start_label: str, end_label: str) -> bool:
    """ 
    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.
    """
    return end_label in self[start_label]
def undirected_edges(self) ‑> list

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

Expand source code
def undirected_edges(self) -> list:
    """ 
    Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
    """
    edges = []
    for start in self._adjacents.keys():
        for end in self.adjacents(start):
            weight = self[start][end]
            if start != end and (end, start, weight) not in edges:  
                edges.append((start, end, weight))
    return edges

Inherited members

class AdjacencyMatrixGraph (labels: list[str])

An unweighted adjacency matrix graph implementation.

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

Initialize the graph with a list of vertex labels.

Args

labels : list[str]
List of labels for each vertex.
Expand source code
class AdjacencyMatrixGraph:
    """ 
    An unweighted adjacency matrix graph implementation.
    
    This class allows either directed or undirected representation of a graph.
    Vertex labels are string types.
    """
    def __init__(self, labels: list[str]):
        """ 
        Initialize the graph with a list of vertex labels.

        Args:
            labels (list[str]): List of labels for each vertex.
        """
        self.labels = labels
        self.label_index = { label: index for index, label  in enumerate(labels) }

        node_count = len(self.labels)
        self.array = [[None for i in range(node_count)] for j in range(node_count)]

    def add_edge(self, a_label: str, b_label: str):
        """ 
        Add an undirected edge between two vertices.
        
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_vertex(a_label, b_label)
        
    def add_adjacent_vertex(self, a_label: str, b_label: str):
        """ 
        Add an undirected edge between two vertices.
        
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]
        self.array[a][b] = True
        self.array[a][a] = True

        self.array[b][a] = True
        self.array[b][b] = True

    def add_directed_edge(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_adjacent_directed_vertex())

        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_directed_vertex(a_label, b_label)

    def add_directed_adjacent_vertex(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_adjacent_directed_vertex()  and add_directed_edge())
 
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        self.add_adjacent_directed_vertex(a_label, b_label)
        
    def add_adjacent_directed_vertex(self, a_label: str, b_label: str):
        """ 
        Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_directed_edge())
 
        Args:
            a_label (str): Starting vertex label.
            b_label (str): Ending vertex label.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]
        self.array[a][b] = True
        self.array[a][a] = True
        self.array[b][b] = True

    def df_traverse(self, start_label: str):
        """ 
        Perform depth first traversal in an adjacency matrix
 
        Args:
            start_label (str): Starting vertex label.
        
        Returns:
            Array with depth first order traversal.
        """
        return self._df_rec_traverse(start_label, set(), [])
        
    def _df_rec_traverse(self, start_label: str, visited, dfs):
        """ 
        Helper method for depth first recursive traversal
        """
        start_index = self.label_index[start_label]
        visited.add(start_label)
        dfs.append(self.labels[start_index])

        for adj_index, is_connected in enumerate(self.array[start_index]):
            adj_label = self.labels[adj_index]
            if is_connected and adj_label not in visited:
                self._df_rec_traverse(adj_label, visited, dfs)

        return dfs

    def bf_traverse(self, start_label: str):
        """ 
        Perform breadth first traversal in an adjacency matrix.
 
        Args:
            start_label (str): Starting vertex label.
        
        Returns:
            Array with breadth first order traversal.
        """
        bfs = []
        q = Queue()
        visited = set()

        start_index = self.label_index[start_label]
        q.enqueue(start_index)
        bfs.append(self.labels[start_index])
        while not q.is_empty():
            current = q.dequeue()
            for i in range(len(self.array)):
                if start_index != i and self.array[current][i] and (i not in visited):
                    visited.add(i)
                    q.enqueue(i)
                    bfs.append(self.labels[i])
        return bfs
    
    def vertices(self) -> list:
        """"
        Return a list of vertex labels of the graph
        """
        return self.labels
    
    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                if self.array[i][j] and i != j:  
                    edges.append((self.labels[i], self.labels[j]))
    
        return edges

    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:  
                    edges.append((self.labels[i], self.labels[j]))
    
        return edges

    def is_edge(self, start: str, end: str) -> bool:
        """ 
        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.
        """
        start_index = self.label_index[start]
        end_index = self.label_index[end]

        return self.array[start_index][end_index]

    def __getitem__(self, vertex: str) -> list:
        """ 
        Args:
            vertex (str): The vertex label.
        Returns:
            A list of adjacent vertex labels.
        """
        index = self.label_index[vertex]
        return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]]
    
    def print_graph(self):
        """ 
        Print the contents of the graph.
        """
        print("   |", end="")
        for label in self.labels:
            print(f"{label:^3}", end=" ")
        print()
        print("----" * (len(self.array) + 1))
        for r, row in enumerate(self.array):
            label = self.labels[r]
            print(f"{label:^3}|", end="")
            for col in row:
                b = " T " if col else "   "
                print(b, end=" ")
            print()

Subclasses

Methods

def add_adjacent_directed_vertex(self, a_label: str, b_label: str)

Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_directed_edge())

Args

a_label : str
Starting vertex label.
b_label : str
Ending vertex label.
Expand source code
def add_adjacent_directed_vertex(self, a_label: str, b_label: str):
    """ 
    Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_directed_edge())

    Args:
        a_label (str): Starting vertex label.
        b_label (str): Ending vertex label.
    """
    a = self.label_index[a_label]
    b = self.label_index[b_label]
    self.array[a][b] = True
    self.array[a][a] = True
    self.array[b][b] = True
def add_adjacent_vertex(self, a_label: str, b_label: str)

Add an undirected edge between two vertices.

Args

a_label : str
Starting vertex label.
b_label : str
Ending vertex label.
Expand source code
def add_adjacent_vertex(self, a_label: str, b_label: str):
    """ 
    Add an undirected edge between two vertices.
    
    Args:
        a_label (str): Starting vertex label.
        b_label (str): Ending vertex label.
    """
    a = self.label_index[a_label]
    b = self.label_index[b_label]
    self.array[a][b] = True
    self.array[a][a] = True

    self.array[b][a] = True
    self.array[b][b] = True
def add_directed_adjacent_vertex(self, a_label: str, b_label: str)

Add a directed edge between one vertex to another (same as add_adjacent_directed_vertex() and add_directed_edge())

Args

a_label : str
Starting vertex label.
b_label : str
Ending vertex label.
Expand source code
def add_directed_adjacent_vertex(self, a_label: str, b_label: str):
    """ 
    Add a directed edge between one vertex to another (same as add_adjacent_directed_vertex()  and add_directed_edge())

    Args:
        a_label (str): Starting vertex label.
        b_label (str): Ending vertex label.
    """
    self.add_adjacent_directed_vertex(a_label, b_label)
def add_directed_edge(self, a_label: str, b_label: str)

Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_adjacent_directed_vertex())

Args

a_label : str
Starting vertex label.
b_label : str
Ending vertex label.
Expand source code
def add_directed_edge(self, a_label: str, b_label: str):
    """ 
    Add a directed edge between one vertex to another (same as add_directed_adjacent_vertex() and add_adjacent_directed_vertex())

    Args:
        a_label (str): Starting vertex label.
        b_label (str): Ending vertex label.
    """
    self.add_adjacent_directed_vertex(a_label, b_label)
def add_edge(self, a_label: str, b_label: str)

Add an undirected edge between two vertices.

Args

a_label : str
Starting vertex label.
b_label : str
Ending vertex label.
Expand source code
def add_edge(self, a_label: str, b_label: str):
    """ 
    Add an undirected edge between two vertices.
    
    Args:
        a_label (str): Starting vertex label.
        b_label (str): Ending vertex label.
    """
    self.add_adjacent_vertex(a_label, b_label)
def bf_traverse(self, start_label: str)

Perform breadth first traversal in an adjacency matrix.

Args

start_label : str
Starting vertex label.

Returns

Array with breadth first order traversal.

Expand source code
def bf_traverse(self, start_label: str):
    """ 
    Perform breadth first traversal in an adjacency matrix.

    Args:
        start_label (str): Starting vertex label.
    
    Returns:
        Array with breadth first order traversal.
    """
    bfs = []
    q = Queue()
    visited = set()

    start_index = self.label_index[start_label]
    q.enqueue(start_index)
    bfs.append(self.labels[start_index])
    while not q.is_empty():
        current = q.dequeue()
        for i in range(len(self.array)):
            if start_index != i and self.array[current][i] and (i not in visited):
                visited.add(i)
                q.enqueue(i)
                bfs.append(self.labels[i])
    return bfs
def df_traverse(self, start_label: str)

Perform depth first traversal in an adjacency matrix

Args

start_label : str
Starting vertex label.

Returns

Array with depth first order traversal.

Expand source code
def df_traverse(self, start_label: str):
    """ 
    Perform depth first traversal in an adjacency matrix

    Args:
        start_label (str): Starting vertex label.
    
    Returns:
        Array with depth first order traversal.
    """
    return self._df_rec_traverse(start_label, set(), [])
def edges(self) ‑> list

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

Expand source code
def edges(self) -> list:
    """ 
    Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
    """
    edges = []
    vertex_count = len(self.labels)
    for i in range(vertex_count):
        for j in range(vertex_count):
            if self.array[i][j] and i != j:  
                edges.append((self.labels[i], self.labels[j]))

    return edges
def is_edge(self, start: str, end: str) ‑> bool

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.

Expand source code
def is_edge(self, start: str, end: str) -> bool:
    """ 
    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.
    """
    start_index = self.label_index[start]
    end_index = self.label_index[end]

    return self.array[start_index][end_index]
def print_graph(self)

Print the contents of the graph.

Expand source code
def print_graph(self):
    """ 
    Print the contents of the graph.
    """
    print("   |", end="")
    for label in self.labels:
        print(f"{label:^3}", end=" ")
    print()
    print("----" * (len(self.array) + 1))
    for r, row in enumerate(self.array):
        label = self.labels[r]
        print(f"{label:^3}|", end="")
        for col in row:
            b = " T " if col else "   "
            print(b, end=" ")
        print()
def undirected_edges(self) ‑> list

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

Expand source code
def undirected_edges(self) -> list:
    """ 
    Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
    """
    edges = []
    vertex_count = len(self.labels)
    for i in range(vertex_count):
        for j in range(vertex_count):
            if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges:  
                edges.append((self.labels[i], self.labels[j]))

    return edges
def vertices(self) ‑> list

" Return a list of vertex labels of the graph

Expand source code
def vertices(self) -> list:
    """"
    Return a list of vertex labels of the graph
    """
    return self.labels
class AdjacencyMatrixWeightedGraph (labels)

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

Args

labels
list of labels for each vertex (string types)
Expand source code
class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph):
    """ 
    A weighted adjacency matrix graph implementation
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self, labels):
        """ 
        Args:
            labels: list of labels for each vertex (string types)
        """
        super().__init__(labels)

    def add_edge(self, a_label: str, b_label: str, weight):
        """ 
        Add an undirected edge between one vertex to another (same as add_edge())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_vertex(a_label, b_label, weight)

    def add_adjacent_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add an undirected edge between one vertex to another (same as add_edge())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]

        self.array[a][b] = weight
        self.array[a][a] = 0

        self.array[b][a] = weight
        self.array[b][b] = 0

    def add_directed_edge(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_adjacent_directed_vertex(), add_directed_adjacent_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_directed_vertex(a_label, b_label, weight)

    def add_directed_adjacent_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_adjacent_directed_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        self.add_adjacent_directed_vertex(a_label, b_label, weight)

    def add_adjacent_directed_vertex(self, a_label: str, b_label: str, weight):
        """ 
        Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_directed_adjacent_vertex())

        Args:
            a_label (str): The starting vertex label.
            b_label (str): The ending vertex label.
            weight: The weight of the vertex.
        """
        a = self.label_index[a_label]
        b = self.label_index[b_label]

        self.array[a][b] = weight
        self.array[a][a] = 0
        self.array[b][b] = 0
        
    def print_graph(self):
        """ 
        Print the contents of the graph.
        """
        print("   |", end="")
        for label in self.labels:
            print(f"{label:>3}", end=" ")
        print()
        print("----" * (len(self.array) + 1))
        for r, row in enumerate(self.array):
            label = self.labels[r]
            print(f"{label:^3}|", end="")
            for col in row:
                w = f"{col:3}" if col is not None else "   "
                print(w, end=" ")
            print()

    def edges(self) -> list:
        """ 
        Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                weight = self.array[i][j]
                if weight and i != j:  
                    edges.append((self.labels[i], self.labels[j], weight))
    
        return edges
    
    def undirected_edges(self) -> list:
        """ 
        Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
        """
        edges = []
        vertex_count = len(self.labels)
        for i in range(vertex_count):
            for j in range(vertex_count):
                weight = self.array[i][j]
                if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:  
                    edges.append((self.labels[i], self.labels[j], weight))
    
        return edges

    def is_edge(self, start_label: str, end_label: str) -> bool:
        """ 
        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.
        """
        return super().is_edge(start_label, end_label) is not None
    
    def weightx(self, start: str, end: str) -> bool:
        """ 
        Return weight of an edge
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            weight value of an edge from start to end
        """
        return super().is_edge(start, end)

    def __getitem__(self, vertex: str) -> dict:
        """ 
        Args:
            vertex: vertex label
        Returns:
            a dictionary of adjacent vertex labels and weights
        """
        index = self.label_index[vertex]
        return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}

Ancestors

Methods

def add_adjacent_directed_vertex(self, a_label: str, b_label: str, weight)

Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_directed_adjacent_vertex())

Args

a_label : str
The starting vertex label.
b_label : str
The ending vertex label.
weight
The weight of the vertex.
Expand source code
def add_adjacent_directed_vertex(self, a_label: str, b_label: str, weight):
    """ 
    Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_directed_adjacent_vertex())

    Args:
        a_label (str): The starting vertex label.
        b_label (str): The ending vertex label.
        weight: The weight of the vertex.
    """
    a = self.label_index[a_label]
    b = self.label_index[b_label]

    self.array[a][b] = weight
    self.array[a][a] = 0
    self.array[b][b] = 0
def add_adjacent_vertex(self, a_label: str, b_label: str, weight)

Add an undirected edge between one vertex to another (same as add_edge())

Args

a_label : str
The starting vertex label.
b_label : str
The ending vertex label.
weight
The weight of the vertex.
Expand source code
def add_adjacent_vertex(self, a_label: str, b_label: str, weight):
    """ 
    Add an undirected edge between one vertex to another (same as add_edge())

    Args:
        a_label (str): The starting vertex label.
        b_label (str): The ending vertex label.
        weight: The weight of the vertex.
    """
    a = self.label_index[a_label]
    b = self.label_index[b_label]

    self.array[a][b] = weight
    self.array[a][a] = 0

    self.array[b][a] = weight
    self.array[b][b] = 0
def add_directed_adjacent_vertex(self, a_label: str, b_label: str, weight)

Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_adjacent_directed_vertex())

Args

a_label : str
The starting vertex label.
b_label : str
The ending vertex label.
weight
The weight of the vertex.
Expand source code
def add_directed_adjacent_vertex(self, a_label: str, b_label: str, weight):
    """ 
    Add a weighted directed edge between one vertex to another (same as add_directed_edge(), add_adjacent_directed_vertex())

    Args:
        a_label (str): The starting vertex label.
        b_label (str): The ending vertex label.
        weight: The weight of the vertex.
    """
    self.add_adjacent_directed_vertex(a_label, b_label, weight)
def add_directed_edge(self, a_label: str, b_label: str, weight)

Add a weighted directed edge between one vertex to another (same as add_adjacent_directed_vertex(), add_directed_adjacent_vertex())

Args

a_label : str
The starting vertex label.
b_label : str
The ending vertex label.
weight
The weight of the vertex.
Expand source code
def add_directed_edge(self, a_label: str, b_label: str, weight):
    """ 
    Add a weighted directed edge between one vertex to another (same as add_adjacent_directed_vertex(), add_directed_adjacent_vertex())

    Args:
        a_label (str): The starting vertex label.
        b_label (str): The ending vertex label.
        weight: The weight of the vertex.
    """
    self.add_adjacent_directed_vertex(a_label, b_label, weight)
def add_edge(self, a_label: str, b_label: str, weight)

Add an undirected edge between one vertex to another (same as add_edge())

Args

a_label : str
The starting vertex label.
b_label : str
The ending vertex label.
weight
The weight of the vertex.
Expand source code
def add_edge(self, a_label: str, b_label: str, weight):
    """ 
    Add an undirected edge between one vertex to another (same as add_edge())

    Args:
        a_label (str): The starting vertex label.
        b_label (str): The ending vertex label.
        weight: The weight of the vertex.
    """
    self.add_adjacent_vertex(a_label, b_label, weight)
def edges(self) ‑> list

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

Expand source code
def edges(self) -> list:
    """ 
    Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
    """
    edges = []
    vertex_count = len(self.labels)
    for i in range(vertex_count):
        for j in range(vertex_count):
            weight = self.array[i][j]
            if weight and i != j:  
                edges.append((self.labels[i], self.labels[j], weight))

    return edges
def is_edge(self, start_label: str, end_label: str) ‑> bool

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.

Expand source code
def is_edge(self, start_label: str, end_label: str) -> bool:
    """ 
    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.
    """
    return super().is_edge(start_label, end_label) is not None
def undirected_edges(self) ‑> list

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

Expand source code
def undirected_edges(self) -> list:
    """ 
    Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
    """
    edges = []
    vertex_count = len(self.labels)
    for i in range(vertex_count):
        for j in range(vertex_count):
            weight = self.array[i][j]
            if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges:  
                edges.append((self.labels[i], self.labels[j], weight))

    return edges
def weightx(self, start: str, end: str) ‑> bool

Return weight of an edge

Args

start_label
starting vertex label
end_label
starting vertex label

Returns

weight value of an edge from start to end

Expand source code
def weightx(self, start: str, end: str) -> bool:
    """ 
    Return weight of an edge
    Args:
        start_label: starting vertex label
        end_label: starting vertex label
    
    Returns:
        weight value of an edge from start to end
    """
    return super().is_edge(start, end)

Inherited members