From f520fb90895f30b53b3969997e42e93ff34e921d Mon Sep 17 00:00:00 2001 From: nith2001 Date: Sat, 13 May 2023 23:11:42 -0700 Subject: [PATCH 01/12] Improved Graph Implementations Provides new implementation for graph_list.py and graph_matrix.py along with pytest suites for each. Fixes #8709 --- graphs/graph_list.py | 224 ++++++++--------- graphs/graph_matrix.py | 173 +++++++++++-- graphs/tests/test_graph_list.py | 395 +++++++++++++++++++++++++++++ graphs/tests/test_graph_matrix.py | 404 ++++++++++++++++++++++++++++++ 4 files changed, 1055 insertions(+), 141 deletions(-) create mode 100644 graphs/tests/test_graph_list.py create mode 100644 graphs/tests/test_graph_matrix.py diff --git a/graphs/graph_list.py b/graphs/graph_list.py index e871f3b8a9d6..0f94950d0115 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Author: OMKAR PATHAK, Nwachukwu Chidiebere +# Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere +# Redesigned and reimplemented by Vikram Nithyanandam -# Use a Python dictionary to construct the graph. +# Use an adjacency list via Python dictionary to construct the graph. from __future__ import annotations from pprint import pformat @@ -14,137 +15,126 @@ class GraphAdjacencyList(Generic[T]): """ Adjacency List type Graph Data Structure that accounts for directed and undirected - Graphs. Initialize graph object indicating whether it's directed or undirected. - - Directed graph example: - >>> d_graph = GraphAdjacencyList() - >>> print(d_graph) - {} - >>> d_graph.add_edge(0, 1) - {0: [1], 1: []} - >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) - {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} - >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} - >>> d_graph - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} - >>> print(repr(d_graph)) - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} - - Undirected graph example: - >>> u_graph = GraphAdjacencyList(directed=False) - >>> u_graph.add_edge(0, 1) - {0: [1], 1: [0]} - >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) - {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} - >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) - {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} - >>> u_graph.add_edge(4, 5) - {0: [1, 2], - 1: [0, 2, 4, 5], - 2: [1, 0, 6, 7], - 4: [1, 5], - 5: [1, 4], - 6: [2], - 7: [2]} - >>> print(u_graph) - {0: [1, 2], - 1: [0, 2, 4, 5], - 2: [1, 0, 6, 7], - 4: [1, 5], - 5: [1, 4], - 6: [2], - 7: [2]} - >>> print(repr(u_graph)) - {0: [1, 2], - 1: [0, 2, 4, 5], - 2: [1, 0, 6, 7], - 4: [1, 5], - 5: [1, 4], - 6: [2], - 7: [2]} - >>> char_graph = GraphAdjacencyList(directed=False) - >>> char_graph.add_edge('a', 'b') - {'a': ['b'], 'b': ['a']} - >>> char_graph.add_edge('b', 'c').add_edge('b', 'e').add_edge('b', 'f') - {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} - >>> char_graph - {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} + Graphs. Initialize graph object indicating whether it's directed or undirected. """ - def __init__(self, directed: bool = True) -> None: + def __init__( + self, vertices: list[T] = [], edges: list[list[T]] = [], directed: bool = True + ) -> None: """ Parameters: directed: (bool) Indicates if graph is directed or undirected. Default is True. """ - - self.adj_list: dict[T, list[T]] = {} # dictionary of lists + self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T self.directed = directed + for vertex in vertices: + self.add_vertex(vertex) + + for edge in edges: + if len(edge) != 2: + raise ValueError(f"Invalid input: {edge} is the wrong length.") + self.add_edge(edge[0], edge[1]) + + def add_vertex(self, vertex: T) -> GraphAdjacencyList[T]: + """ + Adds a vertex to the graph. If the given vertex already exists, a ValueError will + be thrown. + """ + if not self.contains_vertex(vertex): + self.adj_list[vertex] = [] + else: + raise ValueError(f"Incorrect input: {vertex} is already in the graph.") + def add_edge( self, source_vertex: T, destination_vertex: T ) -> GraphAdjacencyList[T]: """ - Connects vertices together. Creates and Edge from source vertex to destination - vertex. - Vertices will be created if not found in graph + Creates an edge from source vertex to destination vertex. If any given vertex doesn't exist + or the edge already exists, a ValueError will be thrown. """ - - if not self.directed: # For undirected graphs - # if both source vertex and destination vertex are both present in the - # adjacency list, add destination vertex to source vertex list of adjacent - # vertices and add source vertex to destination vertex list of adjacent - # vertices. - if source_vertex in self.adj_list and destination_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and not self.contains_edge(source_vertex, destination_vertex) + ): + self.adj_list[source_vertex].append(destination_vertex) + if not self.directed: self.adj_list[destination_vertex].append(source_vertex) - # if only source vertex is present in adjacency list, add destination vertex - # to source vertex list of adjacent vertices, then create a new vertex with - # destination vertex as key and assign a list containing the source vertex - # as it's first adjacent vertex. - elif source_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) - self.adj_list[destination_vertex] = [source_vertex] - # if only destination vertex is present in adjacency list, add source vertex - # to destination vertex list of adjacent vertices, then create a new vertex - # with source vertex as key and assign a list containing the source vertex - # as it's first adjacent vertex. - elif destination_vertex in self.adj_list: - self.adj_list[destination_vertex].append(source_vertex) - self.adj_list[source_vertex] = [destination_vertex] - # if both source vertex and destination vertex are not present in adjacency - # list, create a new vertex with source vertex as key and assign a list - # containing the destination vertex as it's first adjacent vertex also - # create a new vertex with destination vertex as key and assign a list - # containing the source vertex as it's first adjacent vertex. - else: - self.adj_list[source_vertex] = [destination_vertex] - self.adj_list[destination_vertex] = [source_vertex] - else: # For directed graphs - # if both source vertex and destination vertex are present in adjacency - # list, add destination vertex to source vertex list of adjacent vertices. - if source_vertex in self.adj_list and destination_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) - # if only source vertex is present in adjacency list, add destination - # vertex to source vertex list of adjacent vertices and create a new vertex - # with destination vertex as key, which has no adjacent vertex - elif source_vertex in self.adj_list: - self.adj_list[source_vertex].append(destination_vertex) - self.adj_list[destination_vertex] = [] - # if only destination vertex is present in adjacency list, create a new - # vertex with source vertex as key and assign a list containing destination - # vertex as first adjacent vertex - elif destination_vertex in self.adj_list: - self.adj_list[source_vertex] = [destination_vertex] - # if both source vertex and destination vertex are not present in adjacency - # list, create a new vertex with source vertex as key and a list containing - # destination vertex as it's first adjacent vertex. Then create a new vertex - # with destination vertex as key, which has no adjacent vertex + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist \ + OR the requested edge already exists between them." + ) + return self + + def remove_vertex(self, vertex: T) -> GraphAdjacencyList[T]: + """ + Removes the given vertex from the graph and deletes all incoming and outgoing edges from + the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. + """ + if self.contains_vertex(vertex): + if not self.directed: + # If not directed, find all neighboring vertices and delete all references of + # edges connecting to the given vertex + for neighbor in self.adj_list[vertex]: + self.adj_list[neighbor].remove(vertex) else: - self.adj_list[source_vertex] = [destination_vertex] - self.adj_list[destination_vertex] = [] + # If directed, search all neighbors of all vertices and delete all references of + # edges connecting to the given vertex + for edge_list in self.adj_list.values(): + if vertex in edge_list: + edge_list.remove(vertex) - return self + # Finally, delete the given vertex and all of its outgoing edge references + self.adj_list.pop(vertex) + else: + raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + + def remove_edge( + self, source_vertex: T, destination_vertex: T + ) -> GraphAdjacencyList[T]: + """ + Removes the edge between the two vertices. If any given vertex doesn't exist + or the edge does not exist, a ValueError will be thrown. + """ + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and self.contains_edge(source_vertex, destination_vertex) + ): + self.adj_list[source_vertex].remove(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].remove(source_vertex) + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist \ + OR the requested edge does not exists between them." + ) + + def contains_vertex(self, vertex: T) -> bool: + """ + Returns True if the graph contains the vertex, False otherwise. + """ + return vertex in self.adj_list + + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: + """ + Returns True if the graph contains the edge from the source_vertex to the + destination_vertex, False otherwise. If any given vertex doesn't exist, a + ValueError will be thrown. + """ + if self.contains_vertex(source_vertex) and self.contains_vertex( + destination_vertex + ): + return True if destination_vertex in self.adj_list[source_vertex] else False + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist." + ) + + def clear_graph(self) -> None: + self.adj_list: dict[T, list[T]] = {} def __repr__(self) -> str: return pformat(self.adj_list) diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index 4adc6c0bb93b..80c480144c3c 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -1,24 +1,149 @@ -class Graph: - def __init__(self, vertex): - self.vertex = vertex - self.graph = [[0] * vertex for i in range(vertex)] - - def add_edge(self, u, v): - self.graph[u - 1][v - 1] = 1 - self.graph[v - 1][u - 1] = 1 - - def show(self): - for i in self.graph: - for j in i: - print(j, end=" ") - print(" ") - - -g = Graph(100) - -g.add_edge(1, 4) -g.add_edge(4, 2) -g.add_edge(4, 5) -g.add_edge(2, 5) -g.add_edge(5, 3) -g.show() +# Author: Vikram Nithyanandam + +from __future__ import annotations + +from pprint import pformat +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class GraphAdjacencyMatrix(Generic[T]): + def __init__( + self, vertices: list[T] = [], edges: list[list[T]] = [], directed: bool = True + ): + """ + Parameters: + directed: (bool) Indicates if graph is directed or undirected. Default is True. + vertices: (list[T]) The list of vertex names the client wants to pass in. Default is empty. + """ + self.directed = directed + self.vertex_to_index: dict[T, int] = {} + self.adj_matrix: list[list[int]] = [] + + for vertex in vertices: + self.add_vertex(vertex) + + for edge in edges: + if len(edge) != 2: + raise ValueError(f"Invalid input: {edge} is the wrong length.") + self.add_edge(edge[0], edge[1]) + + def add_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Creates an edge from source vertex to destination vertex. If any given vertex doesn't exist + or the edge already exists, a ValueError will be thrown. + """ + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and not self.contains_edge(source_vertex, destination_vertex) + ): + # Get the indices of the corresponding vertices and set their edge value to 1. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 1 + if not self.directed: + self.adj_matrix[v][u] = 1 + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist OR \ + there already exists an edge between them." + ) + + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Removes the edge between the two vertices. If any given vertex doesn't exist or the edge + does not exist, a ValueError will be thrown. + """ + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and self.contains_edge(source_vertex, destination_vertex) + ): + # Get the indices of the corresponding vertices and setting their edge value to 0. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 0 + if not self.directed: + self.adj_matrix[v][u] = 0 + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist \ + OR the requested edge does not exists between them." + ) + + def add_vertex(self, vertex: T) -> None: + """ + Adds a vertex to the graph. If the given vertex already exists, a ValueError will + be thrown. + """ + if not self.contains_vertex(vertex): + # build column for vertex + for row in self.adj_matrix: + row.append(0) + + # build row for vertex and update other data structures + self.adj_matrix.append([0] * (len(self.adj_matrix) + 1)) + self.vertex_to_index[vertex] = len(self.adj_matrix) - 1 + else: + raise ValueError(f"Incorrect input: {vertex} already exists in this graph.") + + def remove_vertex(self, vertex: T) -> None: + """ + Removes the given vertex from the graph and deletes all incoming and outgoing edges from + the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. + """ + if self.contains_vertex(vertex): + # first slide up the rows by deleting the row corresponding to the vertex being deleted. + start_index = self.vertex_to_index[vertex] + self.adj_matrix.pop(start_index) + + # next, slide the columns to the left by deleting the values in the column corresponding + # to the vertex being deleted + for lst in self.adj_matrix: + lst.pop(start_index) + + # final clean up + self.vertex_to_index.pop(vertex) + + # decrement indices for vertices shifted by the deleted vertex in the adj matrix + for vertex in self.vertex_to_index: + if self.vertex_to_index[vertex] >= start_index: + self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 + else: + raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + + def contains_vertex(self, vertex: T) -> bool: + """ + Returns True if the graph contains the vertex, False otherwise. + """ + return vertex in self.vertex_to_index + + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: + """ + Returns True if the graph contains the edge from the source_vertex to the + destination_vertex, False otherwise. If any given vertex doesn't exist, a + ValueError will be thrown. + """ + if self.contains_vertex(source_vertex) and self.contains_vertex( + destination_vertex + ): + u = self.vertex_to_index[source_vertex] + v = self.vertex_to_index[destination_vertex] + return True if self.adj_matrix[u][v] == 1 else False + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist." + ) + + def clear_graph(self) -> None: + """ + Clears all vertices and edges. + """ + self.vertices = [] + self.vertex_to_index: dict[T, int] = {} + self.adj_matrix: list[list[int]] = [] + + def __repr__(self) -> str: + return pformat(self.adj_matrix) + "\n" + pformat(self.vertex_to_index) diff --git a/graphs/tests/test_graph_list.py b/graphs/tests/test_graph_list.py new file mode 100644 index 000000000000..0d5dc17ddc3d --- /dev/null +++ b/graphs/tests/test_graph_list.py @@ -0,0 +1,395 @@ +import sys +import random +from typing import Tuple +import unittest + +sys.path.append("..") +from graph_list import GraphAdjacencyList + + +class TestGraphList(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> Tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices, either increase range or decrease vertex count" + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) + directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph = GraphAdjacencyList(directed=False) + directed_graph = GraphAdjacencyList(directed=True) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) + directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self): + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList(vertices=random_vertices1, directed=False) + directed_graph = GraphAdjacencyList(vertices=random_vertices1, directed=True) + + # test adding and removing vertices + for i in range(len(random_vertices1)): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i in range(len(random_vertices1)): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self): + # generate graphs and graph input + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(len(random_vertices) - 1): + for j in range(i + 1, len(random_vertices)): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse may not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self): + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) + directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self): + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i in range(len(random_edges)): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/tests/test_graph_matrix.py b/graphs/tests/test_graph_matrix.py new file mode 100644 index 000000000000..24df7e595a0e --- /dev/null +++ b/graphs/tests/test_graph_matrix.py @@ -0,0 +1,404 @@ +import sys +import random +from typing import Tuple +import unittest + +sys.path.append("..") +from graph_matrix import GraphAdjacencyMatrix + + +class TestGraphMatrix(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ): + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ): + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ): + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ): + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> Tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices, either increase range or decrease vertex count" + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, directed=False + ) + directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph = GraphAdjacencyMatrix(directed=False) + directed_graph = GraphAdjacencyMatrix(directed=True) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, directed=False + ) + directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self): + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices1, directed=False + ) + directed_graph = GraphAdjacencyMatrix(vertices=random_vertices1, directed=True) + + # test adding and removing vertices + for i in range(len(random_vertices1)): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i in range(len(random_vertices1)): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self): + # generate graphs and graph input + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(len(random_vertices) - 1): + for j in range(i + 1, len(random_vertices)): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse may not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self): + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, directed=False + ) + directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self): + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i in range(len(random_edges)): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() From 2b6b70ffb056e01c55bfde3a21dbe26e21fef1a8 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Mon, 15 May 2023 11:22:13 -0700 Subject: [PATCH 02/12] Graph implementation style fixes, corrections, and refactored tests --- graphs/graph_list.py | 470 ++++++++++++++++++++++++++-- graphs/graph_matrix.py | 487 ++++++++++++++++++++++++++++-- graphs/tests/__init__.py | 0 graphs/tests/test_graph_list.py | 395 ------------------------ graphs/tests/test_graph_matrix.py | 404 ------------------------- 5 files changed, 900 insertions(+), 856 deletions(-) create mode 100644 graphs/tests/__init__.py delete mode 100644 graphs/tests/test_graph_list.py delete mode 100644 graphs/tests/test_graph_matrix.py diff --git a/graphs/graph_list.py b/graphs/graph_list.py index 0f94950d0115..2542ac27328c 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -6,6 +6,8 @@ # Use an adjacency list via Python dictionary to construct the graph. from __future__ import annotations +import random +import unittest from pprint import pformat from typing import Generic, TypeVar @@ -19,7 +21,7 @@ class GraphAdjacencyList(Generic[T]): """ def __init__( - self, vertices: list[T] = [], edges: list[list[T]] = [], directed: bool = True + self, vertices: list[T], edges: list[list[T]], directed: bool = True ) -> None: """ Parameters: @@ -28,6 +30,13 @@ def __init__( self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T self.directed = directed + # None checks + if vertices is None: + vertices = [] + + if edges is None: + edges = [] + for vertex in vertices: self.add_vertex(vertex) @@ -36,22 +45,21 @@ def __init__( raise ValueError(f"Invalid input: {edge} is the wrong length.") self.add_edge(edge[0], edge[1]) - def add_vertex(self, vertex: T) -> GraphAdjacencyList[T]: + def add_vertex(self, vertex: T) -> None: """ - Adds a vertex to the graph. If the given vertex already exists, a ValueError will - be thrown. + Adds a vertex to the graph. If the given vertex already exists, + a ValueError will be thrown. """ if not self.contains_vertex(vertex): self.adj_list[vertex] = [] else: raise ValueError(f"Incorrect input: {vertex} is already in the graph.") - def add_edge( - self, source_vertex: T, destination_vertex: T - ) -> GraphAdjacencyList[T]: + def add_edge(self, source_vertex: T, destination_vertex: T) -> None: """ - Creates an edge from source vertex to destination vertex. If any given vertex doesn't exist - or the edge already exists, a ValueError will be thrown. + Creates an edge from source vertex to destination vertex. If any + given vertex doesn't exist or the edge already exists, a ValueError + will be thrown. """ if ( self.contains_vertex(source_vertex) @@ -63,25 +71,27 @@ def add_edge( self.adj_list[destination_vertex].append(source_vertex) else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist \ - OR the requested edge already exists between them." + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} does not exist OR the requested edge ", + "already exists between them.", ) - return self - def remove_vertex(self, vertex: T) -> GraphAdjacencyList[T]: + def remove_vertex(self, vertex: T) -> None: """ - Removes the given vertex from the graph and deletes all incoming and outgoing edges from - the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. + Removes the given vertex from the graph and deletes all incoming and + outgoing edges from the given vertex as well. If the given vertex + does not exist, a ValueError will be thrown. """ if self.contains_vertex(vertex): if not self.directed: - # If not directed, find all neighboring vertices and delete all references of + # If not directed, find all neighboring vertices and delete + # all references of # edges connecting to the given vertex for neighbor in self.adj_list[vertex]: self.adj_list[neighbor].remove(vertex) else: - # If directed, search all neighbors of all vertices and delete all references of - # edges connecting to the given vertex + # If directed, search all neighbors of all vertices and delete + # all references of edges connecting to the given vertex for edge_list in self.adj_list.values(): if vertex in edge_list: edge_list.remove(vertex) @@ -91,12 +101,10 @@ def remove_vertex(self, vertex: T) -> GraphAdjacencyList[T]: else: raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") - def remove_edge( - self, source_vertex: T, destination_vertex: T - ) -> GraphAdjacencyList[T]: + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: """ - Removes the edge between the two vertices. If any given vertex doesn't exist - or the edge does not exist, a ValueError will be thrown. + Removes the edge between the two vertices. If any given vertex + doesn't exist or the edge does not exist, a ValueError will be thrown. """ if ( self.contains_vertex(source_vertex) @@ -108,8 +116,9 @@ def remove_edge( self.adj_list[destination_vertex].remove(source_vertex) else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist \ - OR the requested edge does not exists between them." + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} do not exist OR the requested edge ", + "does not exists between them.", ) def contains_vertex(self, vertex: T) -> bool: @@ -130,11 +139,418 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: return True if destination_vertex in self.adj_list[source_vertex] else False else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist." + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} does not exist.", ) def clear_graph(self) -> None: - self.adj_list: dict[T, list[T]] = {} + self.adj_list = {} def __repr__(self) -> str: return pformat(self.adj_list) + + +class TestGraphAdjacencyList(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices. Either increase range ", + "between min_val and max_val or decrease vertex count.", + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph = GraphAdjacencyList(vertices=[], edges=[], directed=False) + directed_graph = GraphAdjacencyList(vertices=[], edges=[], directed=True) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self): + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=True + ) + + # test adding and removing vertices + for i in range(len(random_vertices1)): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i in range(len(random_vertices1)): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self): + # generate graphs and graph input + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(len(random_vertices) - 1): + for j in range(i + 1, len(random_vertices)): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse + # may not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self): + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self): + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i in range(len(random_edges)): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index 80c480144c3c..1e8a837123b0 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -1,7 +1,8 @@ # Author: Vikram Nithyanandam - from __future__ import annotations +import random +import unittest from pprint import pformat from typing import Generic, TypeVar @@ -9,37 +10,48 @@ class GraphAdjacencyMatrix(Generic[T]): - def __init__( - self, vertices: list[T] = [], edges: list[list[T]] = [], directed: bool = True - ): + def __init__(self, vertices: list[T], edges: list[list[T]], directed: bool = True): """ Parameters: - directed: (bool) Indicates if graph is directed or undirected. Default is True. - vertices: (list[T]) The list of vertex names the client wants to pass in. Default is empty. + - vertices: (list[T]) The list of vertex names the client wants to + pass in. Default is empty. + - edges: (list[list[T]]) The list of edges the client wants to + pass in. Each edge is a 2-element list. Default is empty. + - directed: (bool) Indicates if graph is directed or undirected. + Default is True. """ self.directed = directed self.vertex_to_index: dict[T, int] = {} self.adj_matrix: list[list[int]] = [] + # None checks + if vertices is None: + vertices = [] + + if edges is None: + edges = [] + for vertex in vertices: self.add_vertex(vertex) for edge in edges: if len(edge) != 2: - raise ValueError(f"Invalid input: {edge} is the wrong length.") + raise ValueError(f"Invalid input: {edge} must have length 2.") self.add_edge(edge[0], edge[1]) def add_edge(self, source_vertex: T, destination_vertex: T) -> None: """ - Creates an edge from source vertex to destination vertex. If any given vertex doesn't exist - or the edge already exists, a ValueError will be thrown. + Creates an edge from source vertex to destination vertex. If any + given vertex doesn't exist or the edge already exists, a ValueError + will be thrown. """ if ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and not self.contains_edge(source_vertex, destination_vertex) ): - # Get the indices of the corresponding vertices and set their edge value to 1. + # Get the indices of the corresponding vertices and set their + # edge value to 1. u: int = self.vertex_to_index[source_vertex] v: int = self.vertex_to_index[destination_vertex] self.adj_matrix[u][v] = 1 @@ -47,21 +59,23 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: self.adj_matrix[v][u] = 1 else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist OR \ - there already exists an edge between them." + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} do not exist OR there already exists", + "an edge between them.", ) def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: """ - Removes the edge between the two vertices. If any given vertex doesn't exist or the edge - does not exist, a ValueError will be thrown. + Removes the edge between the two vertices. If any given vertex + doesn't exist or the edge does not exist, a ValueError will be thrown. """ if ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and self.contains_edge(source_vertex, destination_vertex) ): - # Get the indices of the corresponding vertices and setting their edge value to 0. + # Get the indices of the corresponding vertices and setting + # their edge value to 0. u: int = self.vertex_to_index[source_vertex] v: int = self.vertex_to_index[destination_vertex] self.adj_matrix[u][v] = 0 @@ -69,14 +83,15 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: self.adj_matrix[v][u] = 0 else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist \ - OR the requested edge does not exists between them." + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} does not exist OR the requested ", + "edge does not exists between them.", ) def add_vertex(self, vertex: T) -> None: """ - Adds a vertex to the graph. If the given vertex already exists, a ValueError will - be thrown. + Adds a vertex to the graph. If the given vertex already exists, + a ValueError will be thrown. """ if not self.contains_vertex(vertex): # build column for vertex @@ -91,23 +106,26 @@ def add_vertex(self, vertex: T) -> None: def remove_vertex(self, vertex: T) -> None: """ - Removes the given vertex from the graph and deletes all incoming and outgoing edges from - the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. + Removes the given vertex from the graph and deletes all incoming and + outgoing edges from the given vertex as well. If the given vertex + does not exist, a ValueError will be thrown. """ if self.contains_vertex(vertex): - # first slide up the rows by deleting the row corresponding to the vertex being deleted. + # first slide up the rows by deleting the row corresponding to + # the vertex being deleted. start_index = self.vertex_to_index[vertex] self.adj_matrix.pop(start_index) - # next, slide the columns to the left by deleting the values in the column corresponding - # to the vertex being deleted + # next, slide the columns to the left by deleting the values in + # the column corresponding to the vertex being deleted for lst in self.adj_matrix: lst.pop(start_index) # final clean up self.vertex_to_index.pop(vertex) - # decrement indices for vertices shifted by the deleted vertex in the adj matrix + # decrement indices for vertices shifted by the deleted vertex + # in the adj matrix for vertex in self.vertex_to_index: if self.vertex_to_index[vertex] >= start_index: self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 @@ -134,16 +152,425 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: return True if self.adj_matrix[u][v] == 1 else False else: raise ValueError( - f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist." + f"Incorrect input: Either {source_vertex} ", + f"or {destination_vertex} does not exist.", ) def clear_graph(self) -> None: """ Clears all vertices and edges. """ - self.vertices = [] - self.vertex_to_index: dict[T, int] = {} - self.adj_matrix: list[list[int]] = [] + self.vertex_to_index = {} + self.adj_matrix = [] def __repr__(self) -> str: - return pformat(self.adj_matrix) + "\n" + pformat(self.vertex_to_index) + first = "Adj Matrix:\n" + pformat(self.adj_matrix) + second = "\nVertex to index mapping:\n" + pformat(self.vertex_to_index) + return first + second + + +class TestGraphMatrix(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ): + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + edge: list[int], + ): + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ): + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyMatrix, + directed_graph: GraphAdjacencyMatrix, + vertex: int, + ): + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices. Either increase ", + "range between min_val and max_val or decrease vertex count", + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph = GraphAdjacencyMatrix(vertices=[], edges=[], directed=False) + directed_graph = GraphAdjacencyMatrix(vertices=[], edges=[], directed=True) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self): + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices1, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices1, edges=[], directed=True + ) + + # test adding and removing vertices + for i in range(len(random_vertices1)): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i in range(len(random_vertices1)): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self): + # generate graphs and graph input + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(len(random_vertices) - 1): + for j in range(i + 1, len(random_vertices)): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse may + # not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self): + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyMatrix( + vertices=random_vertices, edges=[], directed=True + ) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self): + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i in range(len(random_edges)): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/tests/__init__.py b/graphs/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/graphs/tests/test_graph_list.py b/graphs/tests/test_graph_list.py deleted file mode 100644 index 0d5dc17ddc3d..000000000000 --- a/graphs/tests/test_graph_list.py +++ /dev/null @@ -1,395 +0,0 @@ -import sys -import random -from typing import Tuple -import unittest - -sys.path.append("..") -from graph_list import GraphAdjacencyList - - -class TestGraphList(unittest.TestCase): - def __assert_graph_edge_exists_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - edge: list[int], - ): - self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_edge_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - edge: list[int], - ): - self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_vertex_exists_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - vertex: int, - ): - self.assertTrue(undirected_graph.contains_vertex(vertex)) - self.assertTrue(directed_graph.contains_vertex(vertex)) - - def __assert_graph_vertex_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - vertex: int, - ): - self.assertFalse(undirected_graph.contains_vertex(vertex)) - self.assertFalse(directed_graph.contains_vertex(vertex)) - - def __generate_random_edges( - self, vertices: list[int], edge_pick_count: int - ) -> list[list[int]]: - self.assertTrue(edge_pick_count <= len(vertices)) - - random_source_vertices: list[int] = random.sample( - vertices[0 : int(len(vertices) / 2)], edge_pick_count - ) - random_destination_vertices: list[int] = random.sample( - vertices[int(len(vertices) / 2) :], edge_pick_count - ) - random_edges: list[list[int]] = [] - - for source in random_source_vertices: - for dest in random_destination_vertices: - random_edges.append([source, dest]) - - return random_edges - - def __generate_graphs( - self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int - ) -> Tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: - if max_val - min_val + 1 < vertex_count: - raise ValueError( - "Will result in duplicate vertices, either increase range or decrease vertex count" - ) - - # generate graph input - random_vertices: list[int] = random.sample( - range(min_val, max_val + 1), vertex_count - ) - random_edges: list[list[int]] = self.__generate_random_edges( - random_vertices, edge_pick_count - ) - - # build graphs - undirected_graph = GraphAdjacencyList( - vertices=random_vertices, edges=random_edges, directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices, edges=random_edges, directed=True - ) - - return undirected_graph, directed_graph, random_vertices, random_edges - - def test_init_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # test graph initialization with vertices and edges - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - self.assertFalse(undirected_graph.directed) - self.assertTrue(directed_graph.directed) - - def test_contains_vertex(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # Build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) - directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) - - # Test contains_vertex - for num in range(101): - self.assertEqual( - num in random_vertices, undirected_graph.contains_vertex(num) - ) - self.assertEqual( - num in random_vertices, directed_graph.contains_vertex(num) - ) - - def test_add_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build empty graphs - undirected_graph = GraphAdjacencyList(directed=False) - directed_graph = GraphAdjacencyList(directed=True) - - # run add_vertex - for num in random_vertices: - undirected_graph.add_vertex(num) - - for num in random_vertices: - directed_graph.add_vertex(num) - - # test add_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - def test_remove_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) - directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) - - # test remove_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - undirected_graph.remove_vertex(num) - directed_graph.remove_vertex(num) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, num - ) - - def test_add_and_remove_vertices_repeatedly(self): - random_vertices1: list[int] = random.sample(range(51), 20) - random_vertices2: list[int] = random.sample(range(51, 101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList(vertices=random_vertices1, directed=False) - directed_graph = GraphAdjacencyList(vertices=random_vertices1, directed=True) - - # test adding and removing vertices - for i in range(len(random_vertices1)): - undirected_graph.add_vertex(random_vertices2[i]) - directed_graph.add_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - undirected_graph.remove_vertex(random_vertices1[i]) - directed_graph.remove_vertex(random_vertices1[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices1[i] - ) - - # remove all vertices - for i in range(len(random_vertices1)): - undirected_graph.remove_vertex(random_vertices2[i]) - directed_graph.remove_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - def test_contains_edge(self): - # generate graphs and graph input - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # generate all possible edges for testing - all_possible_edges: list[list[int]] = [] - for i in range(len(random_vertices) - 1): - for j in range(i + 1, len(random_vertices)): - all_possible_edges.append([random_vertices[i], random_vertices[j]]) - all_possible_edges.append([random_vertices[j], random_vertices[i]]) - - # test contains_edge function - for edge in all_possible_edges: - if edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - elif [edge[1], edge[0]] in random_edges: - # since this edge exists for undirected but the reverse may not exist for directed - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, [edge[1], edge[0]] - ) - else: - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_edge(self): - # generate graph input - random_vertices: list[int] = random.sample(range(101), 15) - random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList(vertices=random_vertices, directed=False) - directed_graph = GraphAdjacencyList(vertices=random_vertices, directed=True) - - # run and test add_edge - for edge in random_edges: - undirected_graph.add_edge(edge[0], edge[1]) - directed_graph.add_edge(edge[0], edge[1]) - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - - def test_remove_edge(self): - # generate graph input and graphs - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # run and test remove_edge - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - undirected_graph.remove_edge(edge[0], edge[1]) - directed_graph.remove_edge(edge[0], edge[1]) - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_and_remove_edges_repeatedly(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # make some more edge options! - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for i in range(len(random_edges)): - undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, more_random_edges[i] - ) - - undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, random_edges[i] - ) - - def test_add_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.add_vertex(vertex) - with self.assertRaises(ValueError): - directed_graph.add_vertex(vertex) - - def test_remove_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for i in range(101): - if i not in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.remove_vertex(i) - with self.assertRaises(ValueError): - directed_graph.remove_vertex(i) - - def test_add_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for edge in random_edges: - with self.assertRaises(ValueError): - undirected_graph.add_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.add_edge(edge[0], edge[1]) - - def test_remove_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for edge in more_random_edges: - with self.assertRaises(ValueError): - undirected_graph.remove_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.remove_edge(edge[0], edge[1]) - - def test_contains_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(vertex, 102) - - with self.assertRaises(ValueError): - undirected_graph.contains_edge(103, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(103, 102) - - -if __name__ == "__main__": - unittest.main() diff --git a/graphs/tests/test_graph_matrix.py b/graphs/tests/test_graph_matrix.py deleted file mode 100644 index 24df7e595a0e..000000000000 --- a/graphs/tests/test_graph_matrix.py +++ /dev/null @@ -1,404 +0,0 @@ -import sys -import random -from typing import Tuple -import unittest - -sys.path.append("..") -from graph_matrix import GraphAdjacencyMatrix - - -class TestGraphMatrix(unittest.TestCase): - def __assert_graph_edge_exists_check( - self, - undirected_graph: GraphAdjacencyMatrix, - directed_graph: GraphAdjacencyMatrix, - edge: list[int], - ): - self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_edge_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyMatrix, - directed_graph: GraphAdjacencyMatrix, - edge: list[int], - ): - self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_vertex_exists_check( - self, - undirected_graph: GraphAdjacencyMatrix, - directed_graph: GraphAdjacencyMatrix, - vertex: int, - ): - self.assertTrue(undirected_graph.contains_vertex(vertex)) - self.assertTrue(directed_graph.contains_vertex(vertex)) - - def __assert_graph_vertex_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyMatrix, - directed_graph: GraphAdjacencyMatrix, - vertex: int, - ): - self.assertFalse(undirected_graph.contains_vertex(vertex)) - self.assertFalse(directed_graph.contains_vertex(vertex)) - - def __generate_random_edges( - self, vertices: list[int], edge_pick_count: int - ) -> list[list[int]]: - self.assertTrue(edge_pick_count <= len(vertices)) - - random_source_vertices: list[int] = random.sample( - vertices[0 : int(len(vertices) / 2)], edge_pick_count - ) - random_destination_vertices: list[int] = random.sample( - vertices[int(len(vertices) / 2) :], edge_pick_count - ) - random_edges: list[list[int]] = [] - - for source in random_source_vertices: - for dest in random_destination_vertices: - random_edges.append([source, dest]) - - return random_edges - - def __generate_graphs( - self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int - ) -> Tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]: - if max_val - min_val + 1 < vertex_count: - raise ValueError( - "Will result in duplicate vertices, either increase range or decrease vertex count" - ) - - # generate graph input - random_vertices: list[int] = random.sample( - range(min_val, max_val + 1), vertex_count - ) - random_edges: list[list[int]] = self.__generate_random_edges( - random_vertices, edge_pick_count - ) - - # build graphs - undirected_graph = GraphAdjacencyMatrix( - vertices=random_vertices, edges=random_edges, directed=False - ) - directed_graph = GraphAdjacencyMatrix( - vertices=random_vertices, edges=random_edges, directed=True - ) - - return undirected_graph, directed_graph, random_vertices, random_edges - - def test_init_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # test graph initialization with vertices and edges - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - - self.assertFalse(undirected_graph.directed) - self.assertTrue(directed_graph.directed) - - def test_contains_vertex(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # Build graphs WITHOUT edges - undirected_graph = GraphAdjacencyMatrix( - vertices=random_vertices, directed=False - ) - directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) - - # Test contains_vertex - for num in range(101): - self.assertEqual( - num in random_vertices, undirected_graph.contains_vertex(num) - ) - self.assertEqual( - num in random_vertices, directed_graph.contains_vertex(num) - ) - - def test_add_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build empty graphs - undirected_graph = GraphAdjacencyMatrix(directed=False) - directed_graph = GraphAdjacencyMatrix(directed=True) - - # run add_vertex - for num in random_vertices: - undirected_graph.add_vertex(num) - - for num in random_vertices: - directed_graph.add_vertex(num) - - # test add_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - def test_remove_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyMatrix( - vertices=random_vertices, directed=False - ) - directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) - - # test remove_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - undirected_graph.remove_vertex(num) - directed_graph.remove_vertex(num) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, num - ) - - def test_add_and_remove_vertices_repeatedly(self): - random_vertices1: list[int] = random.sample(range(51), 20) - random_vertices2: list[int] = random.sample(range(51, 101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyMatrix( - vertices=random_vertices1, directed=False - ) - directed_graph = GraphAdjacencyMatrix(vertices=random_vertices1, directed=True) - - # test adding and removing vertices - for i in range(len(random_vertices1)): - undirected_graph.add_vertex(random_vertices2[i]) - directed_graph.add_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - undirected_graph.remove_vertex(random_vertices1[i]) - directed_graph.remove_vertex(random_vertices1[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices1[i] - ) - - # remove all vertices - for i in range(len(random_vertices1)): - undirected_graph.remove_vertex(random_vertices2[i]) - directed_graph.remove_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - def test_contains_edge(self): - # generate graphs and graph input - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # generate all possible edges for testing - all_possible_edges: list[list[int]] = [] - for i in range(len(random_vertices) - 1): - for j in range(i + 1, len(random_vertices)): - all_possible_edges.append([random_vertices[i], random_vertices[j]]) - all_possible_edges.append([random_vertices[j], random_vertices[i]]) - - # test contains_edge function - for edge in all_possible_edges: - if edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - elif [edge[1], edge[0]] in random_edges: - # since this edge exists for undirected but the reverse may not exist for directed - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, [edge[1], edge[0]] - ) - else: - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_edge(self): - # generate graph input - random_vertices: list[int] = random.sample(range(101), 15) - random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyMatrix( - vertices=random_vertices, directed=False - ) - directed_graph = GraphAdjacencyMatrix(vertices=random_vertices, directed=True) - - # run and test add_edge - for edge in random_edges: - undirected_graph.add_edge(edge[0], edge[1]) - directed_graph.add_edge(edge[0], edge[1]) - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - - def test_remove_edge(self): - # generate graph input and graphs - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # run and test remove_edge - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - undirected_graph.remove_edge(edge[0], edge[1]) - directed_graph.remove_edge(edge[0], edge[1]) - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_and_remove_edges_repeatedly(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # make some more edge options! - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for i in range(len(random_edges)): - undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, more_random_edges[i] - ) - - undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, random_edges[i] - ) - - def test_add_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.add_vertex(vertex) - with self.assertRaises(ValueError): - directed_graph.add_vertex(vertex) - - def test_remove_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for i in range(101): - if i not in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.remove_vertex(i) - with self.assertRaises(ValueError): - directed_graph.remove_vertex(i) - - def test_add_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for edge in random_edges: - with self.assertRaises(ValueError): - undirected_graph.add_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.add_edge(edge[0], edge[1]) - - def test_remove_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for edge in more_random_edges: - with self.assertRaises(ValueError): - undirected_graph.remove_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.remove_edge(edge[0], edge[1]) - - def test_contains_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(vertex, 102) - - with self.assertRaises(ValueError): - undirected_graph.contains_edge(103, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(103, 102) - - -if __name__ == "__main__": - unittest.main() From f741760a754c167cbbeca88bff2def899841a77d Mon Sep 17 00:00:00 2001 From: nith2001 Date: Mon, 15 May 2023 13:51:16 -0700 Subject: [PATCH 03/12] Helpful docs about graph implementation --- graphs/graph_list.py | 33 ++++++++++++++++++++++++++------- graphs/graph_matrix.py | 17 ++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index 2542ac27328c..d721682a3d59 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,9 +1,20 @@ -#!/usr/bin/env python3 - -# Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere -# Redesigned and reimplemented by Vikram Nithyanandam - -# Use an adjacency list via Python dictionary to construct the graph. +""" +Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere +Redesigned and reimplemented by Vikram Nithyanandam + +Description: +The following implementation is a robust unweighted Graph data structure +implemented using an adjacency list. This vertices and edges of this graph can be +effectively initialized and modified while storing your chosen generic +value in each vertex. + +Adjacency List: https://en.wikipedia.org/wiki/Adjacency_list + +Potential Future Ideas: +- Add a flag to set edge weights on and set edge weights +- Make edge weights and vertex values customizable to store whatever the client wants +- Support multigraph functionality if the client wants it +""" from __future__ import annotations import random @@ -25,7 +36,12 @@ def __init__( ) -> None: """ Parameters: - directed: (bool) Indicates if graph is directed or undirected. Default is True. + - vertices: (list[T]) The list of vertex names the client wants to + pass in. Default is empty. + - edges: (list[list[T]]) The list of edges the client wants to + pass in. Each edge is a 2-element list. Default is empty. + - directed: (bool) Indicates if graph is directed or undirected. + Default is True. """ self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T self.directed = directed @@ -144,6 +160,9 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: ) def clear_graph(self) -> None: + """ + Clears all vertices and edges. + """ self.adj_list = {} def __repr__(self) -> str: diff --git a/graphs/graph_matrix.py b/graphs/graph_matrix.py index 1e8a837123b0..39a31d7bba15 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_matrix.py @@ -1,4 +1,19 @@ -# Author: Vikram Nithyanandam +""" +Author: Vikram Nithyanandam + +Description: +The following implementation is a robust unweighted Graph data structure +implemented using an adjacency matrix. This vertices and edges of this graph can be +effectively initialized and modified while storing your chosen generic +value in each vertex. + +Adjacency Matrix: https://mathworld.wolfram.com/AdjacencyMatrix.html + +Potential Future Ideas: +- Add a flag to set edge weights on and set edge weights +- Make edge weights and vertex values customizable to store whatever the client wants +- Support multigraph functionality if the client wants it +""" from __future__ import annotations import random From ef4f4c81020a5d0456a770ac5b3e85d46cb8c946 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Thu, 25 May 2023 00:02:39 -0700 Subject: [PATCH 04/12] Refactored code to separate files and applied enumerate() --- graphs/graph_adj_list.py | 571 +++++++++++++++ .../{graph_matrix.py => graph_adj_matrix.py} | 13 +- graphs/graph_list.py | 667 ++++-------------- 3 files changed, 699 insertions(+), 552 deletions(-) create mode 100644 graphs/graph_adj_list.py rename graphs/{graph_matrix.py => graph_adj_matrix.py} (98%) diff --git a/graphs/graph_adj_list.py b/graphs/graph_adj_list.py new file mode 100644 index 000000000000..203b2c05b667 --- /dev/null +++ b/graphs/graph_adj_list.py @@ -0,0 +1,571 @@ +#!/usr/bin/env python3 +""" +Author: Vikram Nithyanandam + +Description: +The following implementation is a robust unweighted Graph data structure +implemented using an adjacency list. This vertices and edges of this graph can be +effectively initialized and modified while storing your chosen generic +value in each vertex. + +Adjacency List: https://en.wikipedia.org/wiki/Adjacency_list + +Potential Future Ideas: +- Add a flag to set edge weights on and set edge weights +- Make edge weights and vertex values customizable to store whatever the client wants +- Support multigraph functionality if the client wants it +""" +from __future__ import annotations + +import random +import unittest +from pprint import pformat +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class GraphAdjacencyList(Generic[T]): + def __init__( + self, vertices: list[T], edges: list[list[T]], directed: bool = True + ) -> None: + """ + Parameters: + - vertices: (list[T]) The list of vertex names the client wants to + pass in. Default is empty. + - edges: (list[list[T]]) The list of edges the client wants to + pass in. Each edge is a 2-element list. Default is empty. + - directed: (bool) Indicates if graph is directed or undirected. + Default is True. + """ + self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T + self.directed = directed + + # None checks + if vertices is None: + vertices = [] + + if edges is None: + edges = [] + + for vertex in vertices: + self.add_vertex(vertex) + + for edge in edges: + if len(edge) != 2: + raise ValueError(f"Invalid input: {edge} is the wrong length.") + self.add_edge(edge[0], edge[1]) + + def add_vertex(self, vertex: T) -> None: + """ + Adds a vertex to the graph. If the given vertex already exists, + a ValueError will be thrown. + """ + if not self.contains_vertex(vertex): + self.adj_list[vertex] = [] + else: + raise ValueError(f"Incorrect input: {vertex} is already in the graph.") + + def add_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Creates an edge from source vertex to destination vertex. If any + given vertex doesn't exist or the edge already exists, a ValueError + will be thrown. + """ + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and not self.contains_edge(source_vertex, destination_vertex) + ): + self.adj_list[source_vertex].append(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].append(source_vertex) + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} does not exist OR the requested edge ", + "already exists between them.", + ) + + def remove_vertex(self, vertex: T) -> None: + """ + Removes the given vertex from the graph and deletes all incoming and + outgoing edges from the given vertex as well. If the given vertex + does not exist, a ValueError will be thrown. + """ + if self.contains_vertex(vertex): + if not self.directed: + # If not directed, find all neighboring vertices and delete + # all references of + # edges connecting to the given vertex + for neighbor in self.adj_list[vertex]: + self.adj_list[neighbor].remove(vertex) + else: + # If directed, search all neighbors of all vertices and delete + # all references of edges connecting to the given vertex + for edge_list in self.adj_list.values(): + if vertex in edge_list: + edge_list.remove(vertex) + + # Finally, delete the given vertex and all of its outgoing edge references + self.adj_list.pop(vertex) + else: + raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: + """ + Removes the edge between the two vertices. If any given vertex + doesn't exist or the edge does not exist, a ValueError will be thrown. + """ + if ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) + and self.contains_edge(source_vertex, destination_vertex) + ): + self.adj_list[source_vertex].remove(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].remove(source_vertex) + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} do not exist OR the requested edge ", + "does not exists between them.", + ) + + def contains_vertex(self, vertex: T) -> bool: + """ + Returns True if the graph contains the vertex, False otherwise. + """ + return vertex in self.adj_list + + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: + """ + Returns True if the graph contains the edge from the source_vertex to the + destination_vertex, False otherwise. If any given vertex doesn't exist, a + ValueError will be thrown. + """ + if self.contains_vertex(source_vertex) and self.contains_vertex( + destination_vertex + ): + return True if destination_vertex in self.adj_list[source_vertex] else False + else: + raise ValueError( + f"Incorrect input: Either {source_vertex} or ", + f"{destination_vertex} does not exist.", + ) + + def clear_graph(self) -> None: + """ + Clears all vertices and edges. + """ + self.adj_list = {} + + def __repr__(self) -> str: + return pformat(self.adj_list) + + +class TestGraphAdjacencyList(unittest.TestCase): + def __assert_graph_edge_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_edge_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + edge: list[int], + ): + self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) + self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) + self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) + + def __assert_graph_vertex_exists_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertTrue(undirected_graph.contains_vertex(vertex)) + self.assertTrue(directed_graph.contains_vertex(vertex)) + + def __assert_graph_vertex_does_not_exist_check( + self, + undirected_graph: GraphAdjacencyList, + directed_graph: GraphAdjacencyList, + vertex: int, + ): + self.assertFalse(undirected_graph.contains_vertex(vertex)) + self.assertFalse(directed_graph.contains_vertex(vertex)) + + def __generate_random_edges( + self, vertices: list[int], edge_pick_count: int + ) -> list[list[int]]: + self.assertTrue(edge_pick_count <= len(vertices)) + + random_source_vertices: list[int] = random.sample( + vertices[0 : int(len(vertices) / 2)], edge_pick_count + ) + random_destination_vertices: list[int] = random.sample( + vertices[int(len(vertices) / 2) :], edge_pick_count + ) + random_edges: list[list[int]] = [] + + for source in random_source_vertices: + for dest in random_destination_vertices: + random_edges.append([source, dest]) + + return random_edges + + def __generate_graphs( + self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int + ) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: + if max_val - min_val + 1 < vertex_count: + raise ValueError( + "Will result in duplicate vertices. Either increase range ", + "between min_val and max_val or decrease vertex count.", + ) + + # generate graph input + random_vertices: list[int] = random.sample( + range(min_val, max_val + 1), vertex_count + ) + random_edges: list[list[int]] = self.__generate_random_edges( + random_vertices, edge_pick_count + ) + + # build graphs + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=random_edges, directed=True + ) + + return undirected_graph, directed_graph, random_vertices, random_edges + + def test_init_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # test graph initialization with vertices and edges + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + self.assertFalse(undirected_graph.directed) + self.assertTrue(directed_graph.directed) + + def test_contains_vertex(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # Build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # Test contains_vertex + for num in range(101): + self.assertEqual( + num in random_vertices, undirected_graph.contains_vertex(num) + ) + self.assertEqual( + num in random_vertices, directed_graph.contains_vertex(num) + ) + + def test_add_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build empty graphs + undirected_graph = GraphAdjacencyList(vertices=[], edges=[], directed=False) + directed_graph = GraphAdjacencyList(vertices=[], edges=[], directed=True) + + # run add_vertex + for num in random_vertices: + undirected_graph.add_vertex(num) + + for num in random_vertices: + directed_graph.add_vertex(num) + + # test add_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + def test_remove_vertices(self): + random_vertices: list[int] = random.sample(range(101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # test remove_vertex worked + for num in random_vertices: + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, num + ) + + undirected_graph.remove_vertex(num) + directed_graph.remove_vertex(num) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, num + ) + + def test_add_and_remove_vertices_repeatedly(self): + random_vertices1: list[int] = random.sample(range(51), 20) + random_vertices2: list[int] = random.sample(range(51, 101), 20) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices1, edges=[], directed=True + ) + + # test adding and removing vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.add_vertex(random_vertices2[i]) + directed_graph.add_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_exists_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + undirected_graph.remove_vertex(random_vertices1[i]) + directed_graph.remove_vertex(random_vertices1[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices1[i] + ) + + # remove all vertices + for i, _ in enumerate(random_vertices1): + undirected_graph.remove_vertex(random_vertices2[i]) + directed_graph.remove_vertex(random_vertices2[i]) + + self.__assert_graph_vertex_does_not_exist_check( + undirected_graph, directed_graph, random_vertices2[i] + ) + + def test_contains_edge(self): + # generate graphs and graph input + vertex_count = 20 + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(vertex_count, 0, 100, 4) + + # generate all possible edges for testing + all_possible_edges: list[list[int]] = [] + for i in range(vertex_count - 1): + for j in range(i + 1, vertex_count): + all_possible_edges.append([random_vertices[i], random_vertices[j]]) + all_possible_edges.append([random_vertices[j], random_vertices[i]]) + + # test contains_edge function + for edge in all_possible_edges: + if edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + elif [edge[1], edge[0]] in random_edges: + # since this edge exists for undirected but the reverse + # may not exist for directed + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, [edge[1], edge[0]] + ) + else: + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_edge(self): + # generate graph input + random_vertices: list[int] = random.sample(range(101), 15) + random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + + # build graphs WITHOUT edges + undirected_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=False + ) + directed_graph = GraphAdjacencyList( + vertices=random_vertices, edges=[], directed=True + ) + + # run and test add_edge + for edge in random_edges: + undirected_graph.add_edge(edge[0], edge[1]) + directed_graph.add_edge(edge[0], edge[1]) + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + + def test_remove_edge(self): + # generate graph input and graphs + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # run and test remove_edge + for edge in random_edges: + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, edge + ) + undirected_graph.remove_edge(edge[0], edge[1]) + directed_graph.remove_edge(edge[0], edge[1]) + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, edge + ) + + def test_add_and_remove_edges_repeatedly(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + # make some more edge options! + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for i, _ in enumerate(random_edges): + undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) + + self.__assert_graph_edge_exists_check( + undirected_graph, directed_graph, more_random_edges[i] + ) + + undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) + + self.__assert_graph_edge_does_not_exist_check( + undirected_graph, directed_graph, random_edges[i] + ) + + def test_add_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.add_vertex(vertex) + with self.assertRaises(ValueError): + directed_graph.add_vertex(vertex) + + def test_remove_vertex_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for i in range(101): + if i not in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.remove_vertex(i) + with self.assertRaises(ValueError): + directed_graph.remove_vertex(i) + + def test_add_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for edge in random_edges: + with self.assertRaises(ValueError): + undirected_graph.add_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.add_edge(edge[0], edge[1]) + + def test_remove_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + more_random_edges: list[list[int]] = [] + + while len(more_random_edges) != len(random_edges): + edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) + for edge in edges: + if len(more_random_edges) == len(random_edges): + break + elif edge not in more_random_edges and edge not in random_edges: + more_random_edges.append(edge) + + for edge in more_random_edges: + with self.assertRaises(ValueError): + undirected_graph.remove_edge(edge[0], edge[1]) + with self.assertRaises(ValueError): + directed_graph.remove_edge(edge[0], edge[1]) + + def test_contains_edge_exception_check(self): + ( + undirected_graph, + directed_graph, + random_vertices, + random_edges, + ) = self.__generate_graphs(20, 0, 100, 4) + + for vertex in random_vertices: + with self.assertRaises(ValueError): + undirected_graph.contains_edge(vertex, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(vertex, 102) + + with self.assertRaises(ValueError): + undirected_graph.contains_edge(103, 102) + with self.assertRaises(ValueError): + directed_graph.contains_edge(103, 102) + + +if __name__ == "__main__": + unittest.main() diff --git a/graphs/graph_matrix.py b/graphs/graph_adj_matrix.py similarity index 98% rename from graphs/graph_matrix.py rename to graphs/graph_adj_matrix.py index 39a31d7bba15..718bd2536b90 100644 --- a/graphs/graph_matrix.py +++ b/graphs/graph_adj_matrix.py @@ -368,7 +368,7 @@ def test_add_and_remove_vertices_repeatedly(self): ) # test adding and removing vertices - for i in range(len(random_vertices1)): + for i, _ in enumerate(random_vertices1): undirected_graph.add_vertex(random_vertices2[i]) directed_graph.add_vertex(random_vertices2[i]) @@ -384,7 +384,7 @@ def test_add_and_remove_vertices_repeatedly(self): ) # remove all vertices - for i in range(len(random_vertices1)): + for i, _ in enumerate(random_vertices1): undirected_graph.remove_vertex(random_vertices2[i]) directed_graph.remove_vertex(random_vertices2[i]) @@ -394,17 +394,18 @@ def test_add_and_remove_vertices_repeatedly(self): def test_contains_edge(self): # generate graphs and graph input + vertex_count = 20 ( undirected_graph, directed_graph, random_vertices, random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) + ) = self.__generate_graphs(vertex_count, 0, 100, 4) # generate all possible edges for testing all_possible_edges: list[list[int]] = [] - for i in range(len(random_vertices) - 1): - for j in range(i + 1, len(random_vertices)): + for i in range(vertex_count - 1): + for j in range(i + 1, vertex_count): all_possible_edges.append([random_vertices[i], random_vertices[j]]) all_possible_edges.append([random_vertices[j], random_vertices[i]]) @@ -485,7 +486,7 @@ def test_add_and_remove_edges_repeatedly(self): elif edge not in more_random_edges and edge not in random_edges: more_random_edges.append(edge) - for i in range(len(random_edges)): + for i, _ in enumerate(random_edges): undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) diff --git a/graphs/graph_list.py b/graphs/graph_list.py index d721682a3d59..e871f3b8a9d6 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,24 +1,10 @@ -""" -Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere -Redesigned and reimplemented by Vikram Nithyanandam +#!/usr/bin/env python3 -Description: -The following implementation is a robust unweighted Graph data structure -implemented using an adjacency list. This vertices and edges of this graph can be -effectively initialized and modified while storing your chosen generic -value in each vertex. +# Author: OMKAR PATHAK, Nwachukwu Chidiebere -Adjacency List: https://en.wikipedia.org/wiki/Adjacency_list - -Potential Future Ideas: -- Add a flag to set edge weights on and set edge weights -- Make edge weights and vertex values customizable to store whatever the client wants -- Support multigraph functionality if the client wants it -""" +# Use a Python dictionary to construct the graph. from __future__ import annotations -import random -import unittest from pprint import pformat from typing import Generic, TypeVar @@ -28,548 +14,137 @@ class GraphAdjacencyList(Generic[T]): """ Adjacency List type Graph Data Structure that accounts for directed and undirected - Graphs. Initialize graph object indicating whether it's directed or undirected. + Graphs. Initialize graph object indicating whether it's directed or undirected. + + Directed graph example: + >>> d_graph = GraphAdjacencyList() + >>> print(d_graph) + {} + >>> d_graph.add_edge(0, 1) + {0: [1], 1: []} + >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} + >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> d_graph + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(repr(d_graph)) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + + Undirected graph example: + >>> u_graph = GraphAdjacencyList(directed=False) + >>> u_graph.add_edge(0, 1) + {0: [1], 1: [0]} + >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} + >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} + >>> u_graph.add_edge(4, 5) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(u_graph) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(repr(u_graph)) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> char_graph = GraphAdjacencyList(directed=False) + >>> char_graph.add_edge('a', 'b') + {'a': ['b'], 'b': ['a']} + >>> char_graph.add_edge('b', 'c').add_edge('b', 'e').add_edge('b', 'f') + {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} + >>> char_graph + {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} """ - def __init__( - self, vertices: list[T], edges: list[list[T]], directed: bool = True - ) -> None: + def __init__(self, directed: bool = True) -> None: """ Parameters: - - vertices: (list[T]) The list of vertex names the client wants to - pass in. Default is empty. - - edges: (list[list[T]]) The list of edges the client wants to - pass in. Each edge is a 2-element list. Default is empty. - - directed: (bool) Indicates if graph is directed or undirected. - Default is True. + directed: (bool) Indicates if graph is directed or undirected. Default is True. """ - self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T - self.directed = directed - - # None checks - if vertices is None: - vertices = [] - if edges is None: - edges = [] - - for vertex in vertices: - self.add_vertex(vertex) - - for edge in edges: - if len(edge) != 2: - raise ValueError(f"Invalid input: {edge} is the wrong length.") - self.add_edge(edge[0], edge[1]) + self.adj_list: dict[T, list[T]] = {} # dictionary of lists + self.directed = directed - def add_vertex(self, vertex: T) -> None: + def add_edge( + self, source_vertex: T, destination_vertex: T + ) -> GraphAdjacencyList[T]: """ - Adds a vertex to the graph. If the given vertex already exists, - a ValueError will be thrown. + Connects vertices together. Creates and Edge from source vertex to destination + vertex. + Vertices will be created if not found in graph """ - if not self.contains_vertex(vertex): - self.adj_list[vertex] = [] - else: - raise ValueError(f"Incorrect input: {vertex} is already in the graph.") - def add_edge(self, source_vertex: T, destination_vertex: T) -> None: - """ - Creates an edge from source vertex to destination vertex. If any - given vertex doesn't exist or the edge already exists, a ValueError - will be thrown. - """ - if ( - self.contains_vertex(source_vertex) - and self.contains_vertex(destination_vertex) - and not self.contains_edge(source_vertex, destination_vertex) - ): - self.adj_list[source_vertex].append(destination_vertex) - if not self.directed: + if not self.directed: # For undirected graphs + # if both source vertex and destination vertex are both present in the + # adjacency list, add destination vertex to source vertex list of adjacent + # vertices and add source vertex to destination vertex list of adjacent + # vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) self.adj_list[destination_vertex].append(source_vertex) - else: - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist OR the requested edge ", - "already exists between them.", - ) - - def remove_vertex(self, vertex: T) -> None: - """ - Removes the given vertex from the graph and deletes all incoming and - outgoing edges from the given vertex as well. If the given vertex - does not exist, a ValueError will be thrown. - """ - if self.contains_vertex(vertex): - if not self.directed: - # If not directed, find all neighboring vertices and delete - # all references of - # edges connecting to the given vertex - for neighbor in self.adj_list[vertex]: - self.adj_list[neighbor].remove(vertex) + # if only source vertex is present in adjacency list, add destination vertex + # to source vertex list of adjacent vertices, then create a new vertex with + # destination vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [source_vertex] + # if only destination vertex is present in adjacency list, add source vertex + # to destination vertex list of adjacent vertices, then create a new vertex + # with source vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif destination_vertex in self.adj_list: + self.adj_list[destination_vertex].append(source_vertex) + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and assign a list + # containing the destination vertex as it's first adjacent vertex also + # create a new vertex with destination vertex as key and assign a list + # containing the source vertex as it's first adjacent vertex. else: - # If directed, search all neighbors of all vertices and delete - # all references of edges connecting to the given vertex - for edge_list in self.adj_list.values(): - if vertex in edge_list: - edge_list.remove(vertex) - - # Finally, delete the given vertex and all of its outgoing edge references - self.adj_list.pop(vertex) - else: - raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") - - def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: - """ - Removes the edge between the two vertices. If any given vertex - doesn't exist or the edge does not exist, a ValueError will be thrown. - """ - if ( - self.contains_vertex(source_vertex) - and self.contains_vertex(destination_vertex) - and self.contains_edge(source_vertex, destination_vertex) - ): - self.adj_list[source_vertex].remove(destination_vertex) - if not self.directed: - self.adj_list[destination_vertex].remove(source_vertex) - else: - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} do not exist OR the requested edge ", - "does not exists between them.", - ) - - def contains_vertex(self, vertex: T) -> bool: - """ - Returns True if the graph contains the vertex, False otherwise. - """ - return vertex in self.adj_list + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [source_vertex] + else: # For directed graphs + # if both source vertex and destination vertex are present in adjacency + # list, add destination vertex to source vertex list of adjacent vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + # if only source vertex is present in adjacency list, add destination + # vertex to source vertex list of adjacent vertices and create a new vertex + # with destination vertex as key, which has no adjacent vertex + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [] + # if only destination vertex is present in adjacency list, create a new + # vertex with source vertex as key and assign a list containing destination + # vertex as first adjacent vertex + elif destination_vertex in self.adj_list: + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and a list containing + # destination vertex as it's first adjacent vertex. Then create a new vertex + # with destination vertex as key, which has no adjacent vertex + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [] - def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: - """ - Returns True if the graph contains the edge from the source_vertex to the - destination_vertex, False otherwise. If any given vertex doesn't exist, a - ValueError will be thrown. - """ - if self.contains_vertex(source_vertex) and self.contains_vertex( - destination_vertex - ): - return True if destination_vertex in self.adj_list[source_vertex] else False - else: - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist.", - ) - - def clear_graph(self) -> None: - """ - Clears all vertices and edges. - """ - self.adj_list = {} + return self def __repr__(self) -> str: return pformat(self.adj_list) - - -class TestGraphAdjacencyList(unittest.TestCase): - def __assert_graph_edge_exists_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - edge: list[int], - ): - self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_edge_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - edge: list[int], - ): - self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) - self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) - self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) - - def __assert_graph_vertex_exists_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - vertex: int, - ): - self.assertTrue(undirected_graph.contains_vertex(vertex)) - self.assertTrue(directed_graph.contains_vertex(vertex)) - - def __assert_graph_vertex_does_not_exist_check( - self, - undirected_graph: GraphAdjacencyList, - directed_graph: GraphAdjacencyList, - vertex: int, - ): - self.assertFalse(undirected_graph.contains_vertex(vertex)) - self.assertFalse(directed_graph.contains_vertex(vertex)) - - def __generate_random_edges( - self, vertices: list[int], edge_pick_count: int - ) -> list[list[int]]: - self.assertTrue(edge_pick_count <= len(vertices)) - - random_source_vertices: list[int] = random.sample( - vertices[0 : int(len(vertices) / 2)], edge_pick_count - ) - random_destination_vertices: list[int] = random.sample( - vertices[int(len(vertices) / 2) :], edge_pick_count - ) - random_edges: list[list[int]] = [] - - for source in random_source_vertices: - for dest in random_destination_vertices: - random_edges.append([source, dest]) - - return random_edges - - def __generate_graphs( - self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int - ) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: - if max_val - min_val + 1 < vertex_count: - raise ValueError( - "Will result in duplicate vertices. Either increase range ", - "between min_val and max_val or decrease vertex count.", - ) - - # generate graph input - random_vertices: list[int] = random.sample( - range(min_val, max_val + 1), vertex_count - ) - random_edges: list[list[int]] = self.__generate_random_edges( - random_vertices, edge_pick_count - ) - - # build graphs - undirected_graph = GraphAdjacencyList( - vertices=random_vertices, edges=random_edges, directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices, edges=random_edges, directed=True - ) - - return undirected_graph, directed_graph, random_vertices, random_edges - - def test_init_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # test graph initialization with vertices and edges - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - self.assertFalse(undirected_graph.directed) - self.assertTrue(directed_graph.directed) - - def test_contains_vertex(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # Build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=True - ) - - # Test contains_vertex - for num in range(101): - self.assertEqual( - num in random_vertices, undirected_graph.contains_vertex(num) - ) - self.assertEqual( - num in random_vertices, directed_graph.contains_vertex(num) - ) - - def test_add_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build empty graphs - undirected_graph = GraphAdjacencyList(vertices=[], edges=[], directed=False) - directed_graph = GraphAdjacencyList(vertices=[], edges=[], directed=True) - - # run add_vertex - for num in random_vertices: - undirected_graph.add_vertex(num) - - for num in random_vertices: - directed_graph.add_vertex(num) - - # test add_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - def test_remove_vertices(self): - random_vertices: list[int] = random.sample(range(101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=True - ) - - # test remove_vertex worked - for num in random_vertices: - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, num - ) - - undirected_graph.remove_vertex(num) - directed_graph.remove_vertex(num) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, num - ) - - def test_add_and_remove_vertices_repeatedly(self): - random_vertices1: list[int] = random.sample(range(51), 20) - random_vertices2: list[int] = random.sample(range(51, 101), 20) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList( - vertices=random_vertices1, edges=[], directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices1, edges=[], directed=True - ) - - # test adding and removing vertices - for i in range(len(random_vertices1)): - undirected_graph.add_vertex(random_vertices2[i]) - directed_graph.add_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_exists_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - undirected_graph.remove_vertex(random_vertices1[i]) - directed_graph.remove_vertex(random_vertices1[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices1[i] - ) - - # remove all vertices - for i in range(len(random_vertices1)): - undirected_graph.remove_vertex(random_vertices2[i]) - directed_graph.remove_vertex(random_vertices2[i]) - - self.__assert_graph_vertex_does_not_exist_check( - undirected_graph, directed_graph, random_vertices2[i] - ) - - def test_contains_edge(self): - # generate graphs and graph input - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # generate all possible edges for testing - all_possible_edges: list[list[int]] = [] - for i in range(len(random_vertices) - 1): - for j in range(i + 1, len(random_vertices)): - all_possible_edges.append([random_vertices[i], random_vertices[j]]) - all_possible_edges.append([random_vertices[j], random_vertices[i]]) - - # test contains_edge function - for edge in all_possible_edges: - if edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - elif [edge[1], edge[0]] in random_edges: - # since this edge exists for undirected but the reverse - # may not exist for directed - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, [edge[1], edge[0]] - ) - else: - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_edge(self): - # generate graph input - random_vertices: list[int] = random.sample(range(101), 15) - random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - - # build graphs WITHOUT edges - undirected_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=False - ) - directed_graph = GraphAdjacencyList( - vertices=random_vertices, edges=[], directed=True - ) - - # run and test add_edge - for edge in random_edges: - undirected_graph.add_edge(edge[0], edge[1]) - directed_graph.add_edge(edge[0], edge[1]) - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - - def test_remove_edge(self): - # generate graph input and graphs - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # run and test remove_edge - for edge in random_edges: - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, edge - ) - undirected_graph.remove_edge(edge[0], edge[1]) - directed_graph.remove_edge(edge[0], edge[1]) - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, edge - ) - - def test_add_and_remove_edges_repeatedly(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - # make some more edge options! - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for i in range(len(random_edges)): - undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1]) - - self.__assert_graph_edge_exists_check( - undirected_graph, directed_graph, more_random_edges[i] - ) - - undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - directed_graph.remove_edge(random_edges[i][0], random_edges[i][1]) - - self.__assert_graph_edge_does_not_exist_check( - undirected_graph, directed_graph, random_edges[i] - ) - - def test_add_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.add_vertex(vertex) - with self.assertRaises(ValueError): - directed_graph.add_vertex(vertex) - - def test_remove_vertex_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for i in range(101): - if i not in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.remove_vertex(i) - with self.assertRaises(ValueError): - directed_graph.remove_vertex(i) - - def test_add_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for edge in random_edges: - with self.assertRaises(ValueError): - undirected_graph.add_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.add_edge(edge[0], edge[1]) - - def test_remove_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - more_random_edges: list[list[int]] = [] - - while len(more_random_edges) != len(random_edges): - edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) - for edge in edges: - if len(more_random_edges) == len(random_edges): - break - elif edge not in more_random_edges and edge not in random_edges: - more_random_edges.append(edge) - - for edge in more_random_edges: - with self.assertRaises(ValueError): - undirected_graph.remove_edge(edge[0], edge[1]) - with self.assertRaises(ValueError): - directed_graph.remove_edge(edge[0], edge[1]) - - def test_contains_edge_exception_check(self): - ( - undirected_graph, - directed_graph, - random_vertices, - random_edges, - ) = self.__generate_graphs(20, 0, 100, 4) - - for vertex in random_vertices: - with self.assertRaises(ValueError): - undirected_graph.contains_edge(vertex, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(vertex, 102) - - with self.assertRaises(ValueError): - undirected_graph.contains_edge(103, 102) - with self.assertRaises(ValueError): - directed_graph.contains_edge(103, 102) - - -if __name__ == "__main__": - unittest.main() From b64adc1e8d12082807e84866aa70e0d8915fa2bf Mon Sep 17 00:00:00 2001 From: nith2001 Date: Thu, 25 May 2023 11:39:18 -0700 Subject: [PATCH 05/12] Renamed files and refactored code to fail fast --- ...ph_adj_list.py => graph_adjacency_list.py} | 80 ++++++------ ...dj_matrix.py => graph_adjacency_matrix.py} | 114 +++++++++--------- 2 files changed, 95 insertions(+), 99 deletions(-) rename graphs/{graph_adj_list.py => graph_adjacency_list.py} (92%) rename graphs/{graph_adj_matrix.py => graph_adjacency_matrix.py} (89%) diff --git a/graphs/graph_adj_list.py b/graphs/graph_adjacency_list.py similarity index 92% rename from graphs/graph_adj_list.py rename to graphs/graph_adjacency_list.py index 203b2c05b667..0c23162af5be 100644 --- a/graphs/graph_adj_list.py +++ b/graphs/graph_adjacency_list.py @@ -41,12 +41,9 @@ def __init__( self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T self.directed = directed - # None checks - if vertices is None: - vertices = [] - - if edges is None: - edges = [] + # Falsey checks + edges = edges or [] + vertices = vertices or [] for vertex in vertices: self.add_vertex(vertex) @@ -61,10 +58,9 @@ def add_vertex(self, vertex: T) -> None: Adds a vertex to the graph. If the given vertex already exists, a ValueError will be thrown. """ - if not self.contains_vertex(vertex): - self.adj_list[vertex] = [] - else: + if self.contains_vertex(vertex): raise ValueError(f"Incorrect input: {vertex} is already in the graph.") + self.adj_list[vertex] = [] def add_edge(self, source_vertex: T, destination_vertex: T) -> None: """ @@ -72,66 +68,69 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: given vertex doesn't exist or the edge already exists, a ValueError will be thrown. """ - if ( + if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and not self.contains_edge(source_vertex, destination_vertex) ): - self.adj_list[source_vertex].append(destination_vertex) - if not self.directed: - self.adj_list[destination_vertex].append(source_vertex) - else: raise ValueError( f"Incorrect input: Either {source_vertex} or ", f"{destination_vertex} does not exist OR the requested edge ", "already exists between them.", ) + # add the destination vertex to the list associated with the source vertex + # and vice versa if not directed + self.adj_list[source_vertex].append(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].append(source_vertex) + def remove_vertex(self, vertex: T) -> None: """ Removes the given vertex from the graph and deletes all incoming and outgoing edges from the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. """ - if self.contains_vertex(vertex): - if not self.directed: - # If not directed, find all neighboring vertices and delete - # all references of - # edges connecting to the given vertex - for neighbor in self.adj_list[vertex]: - self.adj_list[neighbor].remove(vertex) - else: - # If directed, search all neighbors of all vertices and delete - # all references of edges connecting to the given vertex - for edge_list in self.adj_list.values(): - if vertex in edge_list: - edge_list.remove(vertex) - - # Finally, delete the given vertex and all of its outgoing edge references - self.adj_list.pop(vertex) - else: + if not self.contains_vertex(vertex): raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + if not self.directed: + # If not directed, find all neighboring vertices and delete all references + # of edges connecting to the given vertex + for neighbor in self.adj_list[vertex]: + self.adj_list[neighbor].remove(vertex) + else: + # If directed, search all neighbors of all vertices and delete all + # references of edges connecting to the given vertex + for edge_list in self.adj_list.values(): + if vertex in edge_list: + edge_list.remove(vertex) + + # Finally, delete the given vertex and all of its outgoing edge references + self.adj_list.pop(vertex) + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: """ Removes the edge between the two vertices. If any given vertex doesn't exist or the edge does not exist, a ValueError will be thrown. """ - if ( + if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and self.contains_edge(source_vertex, destination_vertex) ): - self.adj_list[source_vertex].remove(destination_vertex) - if not self.directed: - self.adj_list[destination_vertex].remove(source_vertex) - else: raise ValueError( f"Incorrect input: Either {source_vertex} or ", f"{destination_vertex} do not exist OR the requested edge ", "does not exists between them.", ) + # remove the destination vertex from the list associated with the source + # vertex and vice versa if not directed + self.adj_list[source_vertex].remove(destination_vertex) + if not self.directed: + self.adj_list[destination_vertex].remove(source_vertex) + def contains_vertex(self, vertex: T) -> bool: """ Returns True if the graph contains the vertex, False otherwise. @@ -144,16 +143,17 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: destination_vertex, False otherwise. If any given vertex doesn't exist, a ValueError will be thrown. """ - if self.contains_vertex(source_vertex) and self.contains_vertex( - destination_vertex + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) ): - return True if destination_vertex in self.adj_list[source_vertex] else False - else: raise ValueError( f"Incorrect input: Either {source_vertex} or ", f"{destination_vertex} does not exist.", ) + return destination_vertex in self.adj_list[source_vertex] + def clear_graph(self) -> None: """ Clears all vertices and edges. diff --git a/graphs/graph_adj_matrix.py b/graphs/graph_adjacency_matrix.py similarity index 89% rename from graphs/graph_adj_matrix.py rename to graphs/graph_adjacency_matrix.py index 718bd2536b90..726df47823c0 100644 --- a/graphs/graph_adj_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ Author: Vikram Nithyanandam @@ -39,12 +40,9 @@ def __init__(self, vertices: list[T], edges: list[list[T]], directed: bool = Tru self.vertex_to_index: dict[T, int] = {} self.adj_matrix: list[list[int]] = [] - # None checks - if vertices is None: - vertices = [] - - if edges is None: - edges = [] + # Falsey checks + edges = edges or [] + vertices = vertices or [] for vertex in vertices: self.add_vertex(vertex) @@ -60,93 +58,90 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: given vertex doesn't exist or the edge already exists, a ValueError will be thrown. """ - if ( + if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and not self.contains_edge(source_vertex, destination_vertex) ): - # Get the indices of the corresponding vertices and set their - # edge value to 1. - u: int = self.vertex_to_index[source_vertex] - v: int = self.vertex_to_index[destination_vertex] - self.adj_matrix[u][v] = 1 - if not self.directed: - self.adj_matrix[v][u] = 1 - else: raise ValueError( f"Incorrect input: Either {source_vertex} or ", f"{destination_vertex} do not exist OR there already exists", "an edge between them.", ) + # Get the indices of the corresponding vertices and set their edge value to 1. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 1 + if not self.directed: + self.adj_matrix[v][u] = 1 + def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: """ Removes the edge between the two vertices. If any given vertex doesn't exist or the edge does not exist, a ValueError will be thrown. """ - if ( + if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) and self.contains_edge(source_vertex, destination_vertex) ): - # Get the indices of the corresponding vertices and setting - # their edge value to 0. - u: int = self.vertex_to_index[source_vertex] - v: int = self.vertex_to_index[destination_vertex] - self.adj_matrix[u][v] = 0 - if not self.directed: - self.adj_matrix[v][u] = 0 - else: raise ValueError( f"Incorrect input: Either {source_vertex} or ", f"{destination_vertex} does not exist OR the requested ", "edge does not exists between them.", ) + # Get the indices of the corresponding vertices and set their edge value to 0. + u: int = self.vertex_to_index[source_vertex] + v: int = self.vertex_to_index[destination_vertex] + self.adj_matrix[u][v] = 0 + if not self.directed: + self.adj_matrix[v][u] = 0 + def add_vertex(self, vertex: T) -> None: """ Adds a vertex to the graph. If the given vertex already exists, a ValueError will be thrown. """ - if not self.contains_vertex(vertex): - # build column for vertex - for row in self.adj_matrix: - row.append(0) - - # build row for vertex and update other data structures - self.adj_matrix.append([0] * (len(self.adj_matrix) + 1)) - self.vertex_to_index[vertex] = len(self.adj_matrix) - 1 - else: + if self.contains_vertex(vertex): raise ValueError(f"Incorrect input: {vertex} already exists in this graph.") + # build column for vertex + for row in self.adj_matrix: + row.append(0) + + # build row for vertex and update other data structures + self.adj_matrix.append([0] * (len(self.adj_matrix) + 1)) + self.vertex_to_index[vertex] = len(self.adj_matrix) - 1 + def remove_vertex(self, vertex: T) -> None: """ Removes the given vertex from the graph and deletes all incoming and outgoing edges from the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. """ - if self.contains_vertex(vertex): - # first slide up the rows by deleting the row corresponding to - # the vertex being deleted. - start_index = self.vertex_to_index[vertex] - self.adj_matrix.pop(start_index) - - # next, slide the columns to the left by deleting the values in - # the column corresponding to the vertex being deleted - for lst in self.adj_matrix: - lst.pop(start_index) - - # final clean up - self.vertex_to_index.pop(vertex) - - # decrement indices for vertices shifted by the deleted vertex - # in the adj matrix - for vertex in self.vertex_to_index: - if self.vertex_to_index[vertex] >= start_index: - self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 - else: + if not self.contains_vertex(vertex): raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + # first slide up the rows by deleting the row corresponding to + # the vertex being deleted. + start_index = self.vertex_to_index[vertex] + self.adj_matrix.pop(start_index) + + # next, slide the columns to the left by deleting the values in + # the column corresponding to the vertex being deleted + for lst in self.adj_matrix: + lst.pop(start_index) + + # final clean up + self.vertex_to_index.pop(vertex) + + # decrement indices for vertices shifted by the deleted vertex in the adj matrix + for vertex in self.vertex_to_index: + if self.vertex_to_index[vertex] >= start_index: + self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1 + def contains_vertex(self, vertex: T) -> bool: """ Returns True if the graph contains the vertex, False otherwise. @@ -159,18 +154,19 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: destination_vertex, False otherwise. If any given vertex doesn't exist, a ValueError will be thrown. """ - if self.contains_vertex(source_vertex) and self.contains_vertex( - destination_vertex + if not ( + self.contains_vertex(source_vertex) + and self.contains_vertex(destination_vertex) ): - u = self.vertex_to_index[source_vertex] - v = self.vertex_to_index[destination_vertex] - return True if self.adj_matrix[u][v] == 1 else False - else: raise ValueError( f"Incorrect input: Either {source_vertex} ", f"or {destination_vertex} does not exist.", ) + u = self.vertex_to_index[source_vertex] + v = self.vertex_to_index[destination_vertex] + return True if self.adj_matrix[u][v] == 1 else False + def clear_graph(self) -> None: """ Clears all vertices and edges. From 64adc48726fa21446f67f42a7bae3a02262e2535 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 13:50:50 -0700 Subject: [PATCH 06/12] Error handling style fix --- graphs/graph_adjacency_list.py | 18 ++++++++++++------ graphs/graph_adjacency_matrix.py | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index 0c23162af5be..ed476f6790da 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -71,12 +71,15 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) - and not self.contains_edge(source_vertex, destination_vertex) ): raise ValueError( f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist OR the requested edge ", - "already exists between them.", + f"{destination_vertex} does not exist", + ) + if self.contains_edge(source_vertex, destination_vertex): + raise ValueError( + "Incorrect input: The edge already exists between ", + f"{source_vertex} or {destination_vertex}", ) # add the destination vertex to the list associated with the source vertex @@ -117,12 +120,15 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) - and self.contains_edge(source_vertex, destination_vertex) ): raise ValueError( f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} do not exist OR the requested edge ", - "does not exists between them.", + f"{destination_vertex} does not exist", + ) + if not self.contains_edge(source_vertex, destination_vertex): + raise ValueError( + "Incorrect input: The edge does NOT exist between ", + f"{source_vertex} or {destination_vertex}", ) # remove the destination vertex from the list associated with the source diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 726df47823c0..9383921f48b0 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -61,12 +61,15 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) - and not self.contains_edge(source_vertex, destination_vertex) ): raise ValueError( f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} do not exist OR there already exists", - "an edge between them.", + f"{destination_vertex} does not exist", + ) + elif self.contains_edge(source_vertex, destination_vertex): + raise ValueError( + "Incorrect input: The edge already exists between ", + f"{source_vertex} or {destination_vertex}", ) # Get the indices of the corresponding vertices and set their edge value to 1. @@ -84,12 +87,15 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: if not ( self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) - and self.contains_edge(source_vertex, destination_vertex) ): raise ValueError( f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist OR the requested ", - "edge does not exists between them.", + f"{destination_vertex} does not exist", + ) + elif not self.contains_edge(source_vertex, destination_vertex): + raise ValueError( + "Incorrect input: The edge does NOT exist between ", + f"{source_vertex} or {destination_vertex}", ) # Get the indices of the corresponding vertices and set their edge value to 0. From 438d19ab14161e0a88c52ef573ed4b70dea58893 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 14:17:08 -0700 Subject: [PATCH 07/12] Fixed f-string code quality issue --- graphs/graph_adjacency_list.py | 45 +++++++++++++++----------- graphs/graph_adjacency_matrix.py | 54 ++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index ed476f6790da..372881bc3452 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -59,7 +59,8 @@ def add_vertex(self, vertex: T) -> None: a ValueError will be thrown. """ if self.contains_vertex(vertex): - raise ValueError(f"Incorrect input: {vertex} is already in the graph.") + msg = f"Incorrect input: {vertex} is already in the graph." + raise ValueError(msg) self.adj_list[vertex] = [] def add_edge(self, source_vertex: T, destination_vertex: T) -> None: @@ -72,15 +73,17 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist", + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" ) + raise ValueError(msg) if self.contains_edge(source_vertex, destination_vertex): - raise ValueError( - "Incorrect input: The edge already exists between ", - f"{source_vertex} or {destination_vertex}", + msg = ( + "Incorrect input: The edge already exists between " + f"{source_vertex} or {destination_vertex}" ) + raise ValueError(msg) # add the destination vertex to the list associated with the source vertex # and vice versa if not directed @@ -95,7 +98,8 @@ def remove_vertex(self, vertex: T) -> None: does not exist, a ValueError will be thrown. """ if not self.contains_vertex(vertex): - raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + msg = f"Incorrect input: {vertex} does not exist in this graph." + raise ValueError(msg) if not self.directed: # If not directed, find all neighboring vertices and delete all references @@ -121,15 +125,17 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist", + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" ) + raise ValueError(msg) if not self.contains_edge(source_vertex, destination_vertex): - raise ValueError( - "Incorrect input: The edge does NOT exist between ", - f"{source_vertex} or {destination_vertex}", + msg = ( + "Incorrect input: The edge does NOT exist between " + f"{source_vertex} or {destination_vertex}" ) + raise ValueError(msg) # remove the destination vertex from the list associated with the source # vertex and vice versa if not directed @@ -153,10 +159,11 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist.", + msg = ( + f"Incorrect input: Either {source_vertex} " + f"or {destination_vertex} does not exist." ) + raise ValueError(msg) return destination_vertex in self.adj_list[source_vertex] @@ -233,8 +240,8 @@ def __generate_graphs( ) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]: if max_val - min_val + 1 < vertex_count: raise ValueError( - "Will result in duplicate vertices. Either increase range ", - "between min_val and max_val or decrease vertex count.", + "Will result in duplicate vertices. Either increase range " + "between min_val and max_val or decrease vertex count." ) # generate graph input diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 9383921f48b0..24615bceff37 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -49,7 +49,8 @@ def __init__(self, vertices: list[T], edges: list[list[T]], directed: bool = Tru for edge in edges: if len(edge) != 2: - raise ValueError(f"Invalid input: {edge} must have length 2.") + msg = f"Invalid input: {edge} must have length 2." + raise ValueError(msg) self.add_edge(edge[0], edge[1]) def add_edge(self, source_vertex: T, destination_vertex: T) -> None: @@ -62,15 +63,17 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist", + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" ) - elif self.contains_edge(source_vertex, destination_vertex): - raise ValueError( - "Incorrect input: The edge already exists between ", - f"{source_vertex} or {destination_vertex}", + raise ValueError(msg) + if self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge already exists between " + f"{source_vertex} or {destination_vertex}" ) + raise ValueError(msg) # Get the indices of the corresponding vertices and set their edge value to 1. u: int = self.vertex_to_index[source_vertex] @@ -88,15 +91,17 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} or ", - f"{destination_vertex} does not exist", + msg = ( + f"Incorrect input: Either {source_vertex} or " + f"{destination_vertex} does not exist" ) - elif not self.contains_edge(source_vertex, destination_vertex): - raise ValueError( - "Incorrect input: The edge does NOT exist between ", - f"{source_vertex} or {destination_vertex}", + raise ValueError(msg) + if not self.contains_edge(source_vertex, destination_vertex): + msg = ( + "Incorrect input: The edge does NOT exist between " + f"{source_vertex} or {destination_vertex}" ) + raise ValueError(msg) # Get the indices of the corresponding vertices and set their edge value to 0. u: int = self.vertex_to_index[source_vertex] @@ -111,7 +116,8 @@ def add_vertex(self, vertex: T) -> None: a ValueError will be thrown. """ if self.contains_vertex(vertex): - raise ValueError(f"Incorrect input: {vertex} already exists in this graph.") + msg = f"Incorrect input: {vertex} already exists in this graph." + raise ValueError(msg) # build column for vertex for row in self.adj_matrix: @@ -128,7 +134,8 @@ def remove_vertex(self, vertex: T) -> None: does not exist, a ValueError will be thrown. """ if not self.contains_vertex(vertex): - raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") + msg = f"Incorrect input: {vertex} does not exist in this graph." + raise ValueError(msg) # first slide up the rows by deleting the row corresponding to # the vertex being deleted. @@ -164,14 +171,15 @@ def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: self.contains_vertex(source_vertex) and self.contains_vertex(destination_vertex) ): - raise ValueError( - f"Incorrect input: Either {source_vertex} ", - f"or {destination_vertex} does not exist.", + msg = ( + f"Incorrect input: Either {source_vertex} " + f"or {destination_vertex} does not exist." ) + raise ValueError(msg) u = self.vertex_to_index[source_vertex] v = self.vertex_to_index[destination_vertex] - return True if self.adj_matrix[u][v] == 1 else False + return self.adj_matrix[u][v] == 1 def clear_graph(self) -> None: """ @@ -249,8 +257,8 @@ def __generate_graphs( ) -> tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]: if max_val - min_val + 1 < vertex_count: raise ValueError( - "Will result in duplicate vertices. Either increase ", - "range between min_val and max_val or decrease vertex count", + "Will result in duplicate vertices. Either increase " + "range between min_val and max_val or decrease vertex count" ) # generate graph input From b7f8582378b11387f93a44adfa45ab72a5fa18e1 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 14:23:51 -0700 Subject: [PATCH 08/12] Last f-string fix --- graphs/graph_adjacency_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index 372881bc3452..db010dbe1752 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -50,7 +50,8 @@ def __init__( for edge in edges: if len(edge) != 2: - raise ValueError(f"Invalid input: {edge} is the wrong length.") + msg = f"Invalid input: {edge} is the wrong length." + raise ValueError(msg) self.add_edge(edge[0], edge[1]) def add_vertex(self, vertex: T) -> None: From 5081bcd0e6c2588a34747f33462c9ac34d63b5e0 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 14:36:31 -0700 Subject: [PATCH 09/12] Added return types to test functions and more style fixes --- graphs/graph_adjacency_list.py | 36 ++++++++++++++++++-------------- graphs/graph_adjacency_matrix.py | 36 ++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index db010dbe1752..3121e0a93ee4 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -263,7 +263,7 @@ def __generate_graphs( return undirected_graph, directed_graph, random_vertices, random_edges - def test_init_check(self): + def test_init_check(self) -> None: ( undirected_graph, directed_graph, @@ -284,7 +284,7 @@ def test_init_check(self): self.assertFalse(undirected_graph.directed) self.assertTrue(directed_graph.directed) - def test_contains_vertex(self): + def test_contains_vertex(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # Build graphs WITHOUT edges @@ -304,12 +304,16 @@ def test_contains_vertex(self): num in random_vertices, directed_graph.contains_vertex(num) ) - def test_add_vertices(self): + def test_add_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # build empty graphs - undirected_graph = GraphAdjacencyList(vertices=[], edges=[], directed=False) - directed_graph = GraphAdjacencyList(vertices=[], edges=[], directed=True) + undirected_graph: GraphAdjacencyList = GraphAdjacencyList( + vertices=[], edges=[], directed=False + ) + directed_graph: GraphAdjacencyList = GraphAdjacencyList( + vertices=[], edges=[], directed=True + ) # run add_vertex for num in random_vertices: @@ -324,7 +328,7 @@ def test_add_vertices(self): undirected_graph, directed_graph, num ) - def test_remove_vertices(self): + def test_remove_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # build graphs WITHOUT edges @@ -348,7 +352,7 @@ def test_remove_vertices(self): undirected_graph, directed_graph, num ) - def test_add_and_remove_vertices_repeatedly(self): + def test_add_and_remove_vertices_repeatedly(self) -> None: random_vertices1: list[int] = random.sample(range(51), 20) random_vertices2: list[int] = random.sample(range(51, 101), 20) @@ -385,7 +389,7 @@ def test_add_and_remove_vertices_repeatedly(self): undirected_graph, directed_graph, random_vertices2[i] ) - def test_contains_edge(self): + def test_contains_edge(self) -> None: # generate graphs and graph input vertex_count = 20 ( @@ -419,7 +423,7 @@ def test_contains_edge(self): undirected_graph, directed_graph, edge ) - def test_add_edge(self): + def test_add_edge(self) -> None: # generate graph input random_vertices: list[int] = random.sample(range(101), 15) random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) @@ -440,7 +444,7 @@ def test_add_edge(self): undirected_graph, directed_graph, edge ) - def test_remove_edge(self): + def test_remove_edge(self) -> None: # generate graph input and graphs ( undirected_graph, @@ -460,7 +464,7 @@ def test_remove_edge(self): undirected_graph, directed_graph, edge ) - def test_add_and_remove_edges_repeatedly(self): + def test_add_and_remove_edges_repeatedly(self) -> None: ( undirected_graph, directed_graph, @@ -494,7 +498,7 @@ def test_add_and_remove_edges_repeatedly(self): undirected_graph, directed_graph, random_edges[i] ) - def test_add_vertex_exception_check(self): + def test_add_vertex_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -508,7 +512,7 @@ def test_add_vertex_exception_check(self): with self.assertRaises(ValueError): directed_graph.add_vertex(vertex) - def test_remove_vertex_exception_check(self): + def test_remove_vertex_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -523,7 +527,7 @@ def test_remove_vertex_exception_check(self): with self.assertRaises(ValueError): directed_graph.remove_vertex(i) - def test_add_edge_exception_check(self): + def test_add_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -537,7 +541,7 @@ def test_add_edge_exception_check(self): with self.assertRaises(ValueError): directed_graph.add_edge(edge[0], edge[1]) - def test_remove_edge_exception_check(self): + def test_remove_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -561,7 +565,7 @@ def test_remove_edge_exception_check(self): with self.assertRaises(ValueError): directed_graph.remove_edge(edge[0], edge[1]) - def test_contains_edge_exception_check(self): + def test_contains_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 24615bceff37..92b06b227eaa 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -279,7 +279,7 @@ def __generate_graphs( return undirected_graph, directed_graph, random_vertices, random_edges - def test_init_check(self): + def test_init_check(self) -> None: ( undirected_graph, directed_graph, @@ -301,7 +301,7 @@ def test_init_check(self): self.assertFalse(undirected_graph.directed) self.assertTrue(directed_graph.directed) - def test_contains_vertex(self): + def test_contains_vertex(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # Build graphs WITHOUT edges @@ -321,12 +321,16 @@ def test_contains_vertex(self): num in random_vertices, directed_graph.contains_vertex(num) ) - def test_add_vertices(self): + def test_add_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # build empty graphs - undirected_graph = GraphAdjacencyMatrix(vertices=[], edges=[], directed=False) - directed_graph = GraphAdjacencyMatrix(vertices=[], edges=[], directed=True) + undirected_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix( + vertices=[], edges=[], directed=False + ) + directed_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix( + vertices=[], edges=[], directed=True + ) # run add_vertex for num in random_vertices: @@ -341,7 +345,7 @@ def test_add_vertices(self): undirected_graph, directed_graph, num ) - def test_remove_vertices(self): + def test_remove_vertices(self) -> None: random_vertices: list[int] = random.sample(range(101), 20) # build graphs WITHOUT edges @@ -365,7 +369,7 @@ def test_remove_vertices(self): undirected_graph, directed_graph, num ) - def test_add_and_remove_vertices_repeatedly(self): + def test_add_and_remove_vertices_repeatedly(self) -> None: random_vertices1: list[int] = random.sample(range(51), 20) random_vertices2: list[int] = random.sample(range(51, 101), 20) @@ -402,7 +406,7 @@ def test_add_and_remove_vertices_repeatedly(self): undirected_graph, directed_graph, random_vertices2[i] ) - def test_contains_edge(self): + def test_contains_edge(self) -> None: # generate graphs and graph input vertex_count = 20 ( @@ -436,7 +440,7 @@ def test_contains_edge(self): undirected_graph, directed_graph, edge ) - def test_add_edge(self): + def test_add_edge(self) -> None: # generate graph input random_vertices: list[int] = random.sample(range(101), 15) random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4) @@ -457,7 +461,7 @@ def test_add_edge(self): undirected_graph, directed_graph, edge ) - def test_remove_edge(self): + def test_remove_edge(self) -> None: # generate graph input and graphs ( undirected_graph, @@ -477,7 +481,7 @@ def test_remove_edge(self): undirected_graph, directed_graph, edge ) - def test_add_and_remove_edges_repeatedly(self): + def test_add_and_remove_edges_repeatedly(self) -> None: ( undirected_graph, directed_graph, @@ -511,7 +515,7 @@ def test_add_and_remove_edges_repeatedly(self): undirected_graph, directed_graph, random_edges[i] ) - def test_add_vertex_exception_check(self): + def test_add_vertex_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -525,7 +529,7 @@ def test_add_vertex_exception_check(self): with self.assertRaises(ValueError): directed_graph.add_vertex(vertex) - def test_remove_vertex_exception_check(self): + def test_remove_vertex_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -540,7 +544,7 @@ def test_remove_vertex_exception_check(self): with self.assertRaises(ValueError): directed_graph.remove_vertex(i) - def test_add_edge_exception_check(self): + def test_add_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -554,7 +558,7 @@ def test_add_edge_exception_check(self): with self.assertRaises(ValueError): directed_graph.add_edge(edge[0], edge[1]) - def test_remove_edge_exception_check(self): + def test_remove_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, @@ -578,7 +582,7 @@ def test_remove_edge_exception_check(self): with self.assertRaises(ValueError): directed_graph.remove_edge(edge[0], edge[1]) - def test_contains_edge_exception_check(self): + def test_contains_edge_exception_check(self) -> None: ( undirected_graph, directed_graph, From 115b0e2a08b32e36ca37591d497ffec1d49dc8ec Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 14:40:32 -0700 Subject: [PATCH 10/12] Added more function return types --- graphs/graph_adjacency_matrix.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index 92b06b227eaa..ba91a10cb519 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -26,7 +26,9 @@ class GraphAdjacencyMatrix(Generic[T]): - def __init__(self, vertices: list[T], edges: list[list[T]], directed: bool = True): + def __init__( + self, vertices: list[T], edges: list[list[T]], directed: bool = True + ) -> None: """ Parameters: - vertices: (list[T]) The list of vertex names the client wants to @@ -200,7 +202,7 @@ def __assert_graph_edge_exists_check( undirected_graph: GraphAdjacencyMatrix, directed_graph: GraphAdjacencyMatrix, edge: list[int], - ): + ) -> None: self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) @@ -210,7 +212,7 @@ def __assert_graph_edge_does_not_exist_check( undirected_graph: GraphAdjacencyMatrix, directed_graph: GraphAdjacencyMatrix, edge: list[int], - ): + ) -> None: self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) @@ -220,7 +222,7 @@ def __assert_graph_vertex_exists_check( undirected_graph: GraphAdjacencyMatrix, directed_graph: GraphAdjacencyMatrix, vertex: int, - ): + ) -> None: self.assertTrue(undirected_graph.contains_vertex(vertex)) self.assertTrue(directed_graph.contains_vertex(vertex)) @@ -229,7 +231,7 @@ def __assert_graph_vertex_does_not_exist_check( undirected_graph: GraphAdjacencyMatrix, directed_graph: GraphAdjacencyMatrix, vertex: int, - ): + ) -> None: self.assertFalse(undirected_graph.contains_vertex(vertex)) self.assertFalse(directed_graph.contains_vertex(vertex)) From 0ebfc8c5103f679c843c977f9156d4bcfa12f8fb Mon Sep 17 00:00:00 2001 From: nith2001 Date: Tue, 30 May 2023 14:50:35 -0700 Subject: [PATCH 11/12] Added more function return types pt2 --- graphs/graph_adjacency_list.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index 3121e0a93ee4..0e2a580c64e1 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -184,7 +184,7 @@ def __assert_graph_edge_exists_check( undirected_graph: GraphAdjacencyList, directed_graph: GraphAdjacencyList, edge: list[int], - ): + ) -> None: self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1])) self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0])) self.assertTrue(directed_graph.contains_edge(edge[0], edge[1])) @@ -194,7 +194,7 @@ def __assert_graph_edge_does_not_exist_check( undirected_graph: GraphAdjacencyList, directed_graph: GraphAdjacencyList, edge: list[int], - ): + ) -> None: self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1])) self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0])) self.assertFalse(directed_graph.contains_edge(edge[0], edge[1])) @@ -204,7 +204,7 @@ def __assert_graph_vertex_exists_check( undirected_graph: GraphAdjacencyList, directed_graph: GraphAdjacencyList, vertex: int, - ): + ) -> None: self.assertTrue(undirected_graph.contains_vertex(vertex)) self.assertTrue(directed_graph.contains_vertex(vertex)) @@ -213,7 +213,7 @@ def __assert_graph_vertex_does_not_exist_check( undirected_graph: GraphAdjacencyList, directed_graph: GraphAdjacencyList, vertex: int, - ): + ) -> None: self.assertFalse(undirected_graph.contains_vertex(vertex)) self.assertFalse(directed_graph.contains_vertex(vertex)) From f1426c8bdf764d7f3f4404a60b6343705f8c4849 Mon Sep 17 00:00:00 2001 From: nith2001 Date: Wed, 31 May 2023 10:52:29 -0700 Subject: [PATCH 12/12] Fixed error messages --- graphs/graph_adjacency_list.py | 4 ++-- graphs/graph_adjacency_matrix.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py index 0e2a580c64e1..76f34f845860 100644 --- a/graphs/graph_adjacency_list.py +++ b/graphs/graph_adjacency_list.py @@ -82,7 +82,7 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: if self.contains_edge(source_vertex, destination_vertex): msg = ( "Incorrect input: The edge already exists between " - f"{source_vertex} or {destination_vertex}" + f"{source_vertex} and {destination_vertex}" ) raise ValueError(msg) @@ -134,7 +134,7 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: if not self.contains_edge(source_vertex, destination_vertex): msg = ( "Incorrect input: The edge does NOT exist between " - f"{source_vertex} or {destination_vertex}" + f"{source_vertex} and {destination_vertex}" ) raise ValueError(msg) diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py index ba91a10cb519..4d2e02f737f9 100644 --- a/graphs/graph_adjacency_matrix.py +++ b/graphs/graph_adjacency_matrix.py @@ -73,7 +73,7 @@ def add_edge(self, source_vertex: T, destination_vertex: T) -> None: if self.contains_edge(source_vertex, destination_vertex): msg = ( "Incorrect input: The edge already exists between " - f"{source_vertex} or {destination_vertex}" + f"{source_vertex} and {destination_vertex}" ) raise ValueError(msg) @@ -101,7 +101,7 @@ def remove_edge(self, source_vertex: T, destination_vertex: T) -> None: if not self.contains_edge(source_vertex, destination_vertex): msg = ( "Incorrect input: The edge does NOT exist between " - f"{source_vertex} or {destination_vertex}" + f"{source_vertex} and {destination_vertex}" ) raise ValueError(msg)