Module dsa.graph

Expand source code
from dsa.queue import Queue
from dsa.heap import MinHeap

class AdjacencyMatrixGraph:
    """ 
    An unweighted adjacency matrix graph implementation in Python
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self, labels: list[str]):
        """ 
        Args:
            labels: 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 one vertex to another (same as add_edge())

        Args:
            a_label: starting vertex label
            b_label: 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 one vertex to another (same as add_adjacent_vertex())

        Args:
            a_label: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
        """
        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: 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 is_edge(self, start: str, end: str) -> bool:
        """ 
        Return boolean if an edge exists
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            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: 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 in Python
    (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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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 is_edge(self, start: str, end: str) -> bool:
        """ 
        Return boolean if an edge exists
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            boolean of whether there is an edge from start to end
        """
        return super().is_edge(start, end) 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 in Python
    (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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
        """
        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: 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: 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: beginning vertex
            end: vertex to search for
        Returns:
        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: beginning vertex
            end_label: vertex to search for
        Returns:
        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: starting vertex label
            end_label: starting vertex label
        
        Returns:
            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):
        """ 
        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

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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: beginning vertex
            end_label: vertex 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):
        """ 
        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: starting vertex label
            end_label: starting vertex label
        
        Returns:
            boolean of whether there is an edge from start to end
        """
        return end_label in self[start_label]


#### Dijkstra's Algorithm Functions
def shortest_path(graph, start: str, end: str, debug=False):
    """ 
    Helper function that returns a weight table and a previous vertex table using Dijkstra's Algorithm.

    Args:
        graph: adjacency list graph
        start: starting vertex
        end: ending vertex
        debug: if True, display weight table as it is being built
    
    Returns:
    a tuple of a weight table dictionary and a previous path dictionary
    """
    weight_table = {}
    previous = {}
    visited = set()
    h = MinHeap()

    current = start
    h.insert(current)
    weight_table[current] = 0
    previous[current] = current
    
    while not h.is_empty():
        current_weight = weight_table.get(current, float('inf'))
        visited.add(current)

        for adjacent in graph[current]:
            weight = graph[current][adjacent]
            if adjacent not in visited:
                h.insert(adjacent)

            wt = weight_table.get(adjacent, float('inf'))
            if wt > weight + current_weight:
                weight_table[adjacent] = weight + current_weight
                previous[adjacent] = current
                if debug:
                    print(weight_table)

        current = h.pop()

    return weight_table, previous

def find_path(graph, start: str, end: str, debug=False):
    """ 
    Return the shortest path of two vertices using Dijkstra's Algorithm.

    Args:
        graph: graph object (adjacency list)
        start: starting vertex
        end: ending vertex
        debug: if True, display the weight table 

    Returns:
    A list of vertices that form a shortest path
    """
    weight_table, previous = shortest_path(graph, start, end, debug)
    path = []

    current = end
    path.append(current)
    while current != start:
        current = previous[current]
        path.append(current)
        
    path.reverse()

    if debug:
        print("previous table")
        print(previous)

        print("weight table")
        print(weight_table)
        print("price ", weight_table[end])
    return path

Functions

def find_path(graph, start: str, end: str, debug=False)

Return the shortest path of two vertices using Dijkstra's Algorithm.

Args

graph
graph object (adjacency list)
start
starting vertex
end
ending vertex
debug
if True, display the weight table

Returns: A list of vertices that form a shortest path

Expand source code
def find_path(graph, start: str, end: str, debug=False):
    """ 
    Return the shortest path of two vertices using Dijkstra's Algorithm.

    Args:
        graph: graph object (adjacency list)
        start: starting vertex
        end: ending vertex
        debug: if True, display the weight table 

    Returns:
    A list of vertices that form a shortest path
    """
    weight_table, previous = shortest_path(graph, start, end, debug)
    path = []

    current = end
    path.append(current)
    while current != start:
        current = previous[current]
        path.append(current)
        
    path.reverse()

    if debug:
        print("previous table")
        print(previous)

        print("weight table")
        print(weight_table)
        print("price ", weight_table[end])
    return path
def shortest_path(graph, start: str, end: str, debug=False)

Helper function that returns a weight table and a previous vertex table using Dijkstra's Algorithm.

Args

graph
adjacency list graph
start
starting vertex
end
ending vertex
debug
if True, display weight table as it is being built

Returns: a tuple of a weight table dictionary and a previous path dictionary

Expand source code
def shortest_path(graph, start: str, end: str, debug=False):
    """ 
    Helper function that returns a weight table and a previous vertex table using Dijkstra's Algorithm.

    Args:
        graph: adjacency list graph
        start: starting vertex
        end: ending vertex
        debug: if True, display weight table as it is being built
    
    Returns:
    a tuple of a weight table dictionary and a previous path dictionary
    """
    weight_table = {}
    previous = {}
    visited = set()
    h = MinHeap()

    current = start
    h.insert(current)
    weight_table[current] = 0
    previous[current] = current
    
    while not h.is_empty():
        current_weight = weight_table.get(current, float('inf'))
        visited.add(current)

        for adjacent in graph[current]:
            weight = graph[current][adjacent]
            if adjacent not in visited:
                h.insert(adjacent)

            wt = weight_table.get(adjacent, float('inf'))
            if wt > weight + current_weight:
                weight_table[adjacent] = weight + current_weight
                previous[adjacent] = current
                if debug:
                    print(weight_table)

        current = h.pop()

    return weight_table, previous

Classes

class AdjacencyListGraph

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

Expand source code
class AdjacencyListGraph:
    """ 
    A unweighted adjacency list vertex implementation in Python
    (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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
            end_label: ending vertex label 
        """
        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: starting vertex label 
        """
        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: 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: 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: beginning vertex
            end: vertex to search for
        Returns:
        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: beginning vertex
            end_label: vertex to search for
        Returns:
        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: starting vertex label
            end_label: starting vertex label
        
        Returns:
            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):
        """ 
        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

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
starting vertex label
end_label
ending vertex label
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: starting vertex label 
        end_label: ending vertex label 
    """
    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
starting vertex label
end_label
ending vertex label
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: starting vertex label 
        end_label: ending vertex label 
    """
    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
starting vertex label
end_label
ending vertex label
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: starting vertex label 
        end_label: ending vertex label 
    """
    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
starting vertex label
end_label
ending vertex label
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: starting vertex label 
        end_label: ending vertex label 
    """
    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
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, start_label: str)

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

Args

start_label
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: 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
beginning vertex
end_label
vertex to search for

Returns: 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: beginning vertex
        end_label: vertex to search for
    Returns:
    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
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: 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
beginning vertex
end
vertex to search for

Returns: 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: beginning vertex
        end: vertex to search for
    Returns:
    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)

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

Expand source code
def edges(self):
    """ 
    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
starting vertex label
end_label
starting vertex label

Returns

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: starting vertex label
        end_label: starting vertex label
    
    Returns:
        boolean of whether there is an edge from start to end
    """
    return end_label in self[start_label]
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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: starting vertex label 
            end: ending vertex label 
            weight: 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: beginning vertex
            end_label: vertex 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):
        """ 
        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: starting vertex label
            end_label: starting vertex label
        
        Returns:
            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
starting vertex label
end
ending vertex label
weight
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: starting vertex label 
        end: ending vertex label 
        weight: 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
starting vertex label
end
ending vertex label
weight
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: starting vertex label 
        end: ending vertex label 
        weight: 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
starting vertex label
end
ending vertex label
weight
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: starting vertex label 
        end: ending vertex label 
        weight: 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
starting vertex label
end
ending vertex label
weight
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: starting vertex label 
        end: ending vertex label 
        weight: edge weight
    """
    self.add_adjacent_vertex(start_label, end_label, weight)
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
beginning vertex
end_label
vertex 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: beginning vertex
        end_label: vertex 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)

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

Expand source code
def edges(self):
    """ 
    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

Inherited members

class AdjacencyMatrixGraph (labels: list[str])

An unweighted adjacency matrix graph implementation in Python (allows either directed or undirected representation) vertex labels are string types

Args

labels
list of labels for each vertex
Expand source code
class AdjacencyMatrixGraph:
    """ 
    An unweighted adjacency matrix graph implementation in Python
    (allows either directed or undirected representation)
    vertex labels are string types
    """
    def __init__(self, labels: list[str]):
        """ 
        Args:
            labels: 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 one vertex to another (same as add_edge())

        Args:
            a_label: starting vertex label
            b_label: 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 one vertex to another (same as add_adjacent_vertex())

        Args:
            a_label: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
            b_label: 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: starting vertex label
        """
        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: 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 is_edge(self, start: str, end: str) -> bool:
        """ 
        Return boolean if an edge exists
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            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: 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
starting vertex label
b_label
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: starting vertex label
        b_label: 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 one vertex to another (same as add_adjacent_vertex())

Args

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

    Args:
        a_label: starting vertex label
        b_label: 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
starting vertex label
b_label
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: starting vertex label
        b_label: 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
starting vertex label
b_label
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: starting vertex label
        b_label: 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 one vertex to another (same as add_edge())

Args

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

    Args:
        a_label: starting vertex label
        b_label: 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
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: 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
starting vertex label
Expand source code
def df_traverse(self, start_label: str):
    """ 
    Perform depth first traversal in an adjacency matrix

    Args:
        start_label: starting vertex label
    """
    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
starting vertex label
end_label
starting vertex label

Returns

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: starting vertex label
        end_label: starting vertex label
    
    Returns:
        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 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 in Python (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 in Python
    (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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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: starting vertex label
            b_label: ending vertex label
            weight: 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 is_edge(self, start: str, end: str) -> bool:
        """ 
        Return boolean if an edge exists
        Args:
            start_label: starting vertex label
            end_label: starting vertex label
        
        Returns:
            boolean of whether there is an edge from start to end
        """
        return super().is_edge(start, end) 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
starting vertex label
b_label
ending vertex label
weight
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: starting vertex label
        b_label: ending vertex label
        weight: 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
starting vertex label
b_label
ending vertex label
weight
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: starting vertex label
        b_label: ending vertex label
        weight: 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
starting vertex label
b_label
ending vertex label
weight
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: starting vertex label
        b_label: ending vertex label
        weight: 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
starting vertex label
b_label
ending vertex label
weight
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: starting vertex label
        b_label: ending vertex label
        weight: 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
starting vertex label
b_label
ending vertex label
weight
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: starting vertex label
        b_label: ending vertex label
        weight: 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 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:
            w = f"{col:3}" if col is not None else "   "
            print(w, end=" ")
        print()
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