|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 |
|
3 |
| -# Author: OMKAR PATHAK, Nwachukwu Chidiebere |
| 3 | +# Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere |
| 4 | +# Redesigned and reimplemented by Vikram Nithyanandam |
4 | 5 |
|
5 |
| -# Use a Python dictionary to construct the graph. |
| 6 | +# Use an adjacency list via Python dictionary to construct the graph. |
6 | 7 | from __future__ import annotations
|
7 | 8 |
|
8 | 9 | from pprint import pformat
|
|
14 | 15 | class GraphAdjacencyList(Generic[T]):
|
15 | 16 | """
|
16 | 17 | Adjacency List type Graph Data Structure that accounts for directed and undirected
|
17 |
| - Graphs. Initialize graph object indicating whether it's directed or undirected. |
18 |
| -
|
19 |
| - Directed graph example: |
20 |
| - >>> d_graph = GraphAdjacencyList() |
21 |
| - >>> print(d_graph) |
22 |
| - {} |
23 |
| - >>> d_graph.add_edge(0, 1) |
24 |
| - {0: [1], 1: []} |
25 |
| - >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) |
26 |
| - {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} |
27 |
| - >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) |
28 |
| - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} |
29 |
| - >>> d_graph |
30 |
| - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} |
31 |
| - >>> print(repr(d_graph)) |
32 |
| - {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} |
33 |
| -
|
34 |
| - Undirected graph example: |
35 |
| - >>> u_graph = GraphAdjacencyList(directed=False) |
36 |
| - >>> u_graph.add_edge(0, 1) |
37 |
| - {0: [1], 1: [0]} |
38 |
| - >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) |
39 |
| - {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} |
40 |
| - >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) |
41 |
| - {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} |
42 |
| - >>> u_graph.add_edge(4, 5) |
43 |
| - {0: [1, 2], |
44 |
| - 1: [0, 2, 4, 5], |
45 |
| - 2: [1, 0, 6, 7], |
46 |
| - 4: [1, 5], |
47 |
| - 5: [1, 4], |
48 |
| - 6: [2], |
49 |
| - 7: [2]} |
50 |
| - >>> print(u_graph) |
51 |
| - {0: [1, 2], |
52 |
| - 1: [0, 2, 4, 5], |
53 |
| - 2: [1, 0, 6, 7], |
54 |
| - 4: [1, 5], |
55 |
| - 5: [1, 4], |
56 |
| - 6: [2], |
57 |
| - 7: [2]} |
58 |
| - >>> print(repr(u_graph)) |
59 |
| - {0: [1, 2], |
60 |
| - 1: [0, 2, 4, 5], |
61 |
| - 2: [1, 0, 6, 7], |
62 |
| - 4: [1, 5], |
63 |
| - 5: [1, 4], |
64 |
| - 6: [2], |
65 |
| - 7: [2]} |
66 |
| - >>> char_graph = GraphAdjacencyList(directed=False) |
67 |
| - >>> char_graph.add_edge('a', 'b') |
68 |
| - {'a': ['b'], 'b': ['a']} |
69 |
| - >>> char_graph.add_edge('b', 'c').add_edge('b', 'e').add_edge('b', 'f') |
70 |
| - {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} |
71 |
| - >>> char_graph |
72 |
| - {'a': ['b'], 'b': ['a', 'c', 'e', 'f'], 'c': ['b'], 'e': ['b'], 'f': ['b']} |
| 18 | + Graphs. Initialize graph object indicating whether it's directed or undirected. |
73 | 19 | """
|
74 | 20 |
|
75 |
| - def __init__(self, directed: bool = True) -> None: |
| 21 | + def __init__( |
| 22 | + self, vertices: list[T] = [], edges: list[list[T]] = [], directed: bool = True |
| 23 | + ) -> None: |
76 | 24 | """
|
77 | 25 | Parameters:
|
78 | 26 | directed: (bool) Indicates if graph is directed or undirected. Default is True.
|
79 | 27 | """
|
80 |
| - |
81 |
| - self.adj_list: dict[T, list[T]] = {} # dictionary of lists |
| 28 | + self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T |
82 | 29 | self.directed = directed
|
83 | 30 |
|
| 31 | + for vertex in vertices: |
| 32 | + self.add_vertex(vertex) |
| 33 | + |
| 34 | + for edge in edges: |
| 35 | + if len(edge) != 2: |
| 36 | + raise ValueError(f"Invalid input: {edge} is the wrong length.") |
| 37 | + self.add_edge(edge[0], edge[1]) |
| 38 | + |
| 39 | + def add_vertex(self, vertex: T) -> GraphAdjacencyList[T]: |
| 40 | + """ |
| 41 | + Adds a vertex to the graph. If the given vertex already exists, a ValueError will |
| 42 | + be thrown. |
| 43 | + """ |
| 44 | + if not self.contains_vertex(vertex): |
| 45 | + self.adj_list[vertex] = [] |
| 46 | + else: |
| 47 | + raise ValueError(f"Incorrect input: {vertex} is already in the graph.") |
| 48 | + |
84 | 49 | def add_edge(
|
85 | 50 | self, source_vertex: T, destination_vertex: T
|
86 | 51 | ) -> GraphAdjacencyList[T]:
|
87 | 52 | """
|
88 |
| - Connects vertices together. Creates and Edge from source vertex to destination |
89 |
| - vertex. |
90 |
| - Vertices will be created if not found in graph |
| 53 | + Creates an edge from source vertex to destination vertex. If any given vertex doesn't exist |
| 54 | + or the edge already exists, a ValueError will be thrown. |
91 | 55 | """
|
92 |
| - |
93 |
| - if not self.directed: # For undirected graphs |
94 |
| - # if both source vertex and destination vertex are both present in the |
95 |
| - # adjacency list, add destination vertex to source vertex list of adjacent |
96 |
| - # vertices and add source vertex to destination vertex list of adjacent |
97 |
| - # vertices. |
98 |
| - if source_vertex in self.adj_list and destination_vertex in self.adj_list: |
99 |
| - self.adj_list[source_vertex].append(destination_vertex) |
| 56 | + if ( |
| 57 | + self.contains_vertex(source_vertex) |
| 58 | + and self.contains_vertex(destination_vertex) |
| 59 | + and not self.contains_edge(source_vertex, destination_vertex) |
| 60 | + ): |
| 61 | + self.adj_list[source_vertex].append(destination_vertex) |
| 62 | + if not self.directed: |
100 | 63 | self.adj_list[destination_vertex].append(source_vertex)
|
101 |
| - # if only source vertex is present in adjacency list, add destination vertex |
102 |
| - # to source vertex list of adjacent vertices, then create a new vertex with |
103 |
| - # destination vertex as key and assign a list containing the source vertex |
104 |
| - # as it's first adjacent vertex. |
105 |
| - elif source_vertex in self.adj_list: |
106 |
| - self.adj_list[source_vertex].append(destination_vertex) |
107 |
| - self.adj_list[destination_vertex] = [source_vertex] |
108 |
| - # if only destination vertex is present in adjacency list, add source vertex |
109 |
| - # to destination vertex list of adjacent vertices, then create a new vertex |
110 |
| - # with source vertex as key and assign a list containing the source vertex |
111 |
| - # as it's first adjacent vertex. |
112 |
| - elif destination_vertex in self.adj_list: |
113 |
| - self.adj_list[destination_vertex].append(source_vertex) |
114 |
| - self.adj_list[source_vertex] = [destination_vertex] |
115 |
| - # if both source vertex and destination vertex are not present in adjacency |
116 |
| - # list, create a new vertex with source vertex as key and assign a list |
117 |
| - # containing the destination vertex as it's first adjacent vertex also |
118 |
| - # create a new vertex with destination vertex as key and assign a list |
119 |
| - # containing the source vertex as it's first adjacent vertex. |
120 |
| - else: |
121 |
| - self.adj_list[source_vertex] = [destination_vertex] |
122 |
| - self.adj_list[destination_vertex] = [source_vertex] |
123 |
| - else: # For directed graphs |
124 |
| - # if both source vertex and destination vertex are present in adjacency |
125 |
| - # list, add destination vertex to source vertex list of adjacent vertices. |
126 |
| - if source_vertex in self.adj_list and destination_vertex in self.adj_list: |
127 |
| - self.adj_list[source_vertex].append(destination_vertex) |
128 |
| - # if only source vertex is present in adjacency list, add destination |
129 |
| - # vertex to source vertex list of adjacent vertices and create a new vertex |
130 |
| - # with destination vertex as key, which has no adjacent vertex |
131 |
| - elif source_vertex in self.adj_list: |
132 |
| - self.adj_list[source_vertex].append(destination_vertex) |
133 |
| - self.adj_list[destination_vertex] = [] |
134 |
| - # if only destination vertex is present in adjacency list, create a new |
135 |
| - # vertex with source vertex as key and assign a list containing destination |
136 |
| - # vertex as first adjacent vertex |
137 |
| - elif destination_vertex in self.adj_list: |
138 |
| - self.adj_list[source_vertex] = [destination_vertex] |
139 |
| - # if both source vertex and destination vertex are not present in adjacency |
140 |
| - # list, create a new vertex with source vertex as key and a list containing |
141 |
| - # destination vertex as it's first adjacent vertex. Then create a new vertex |
142 |
| - # with destination vertex as key, which has no adjacent vertex |
| 64 | + else: |
| 65 | + raise ValueError( |
| 66 | + f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist \ |
| 67 | + OR the requested edge already exists between them." |
| 68 | + ) |
| 69 | + return self |
| 70 | + |
| 71 | + def remove_vertex(self, vertex: T) -> GraphAdjacencyList[T]: |
| 72 | + """ |
| 73 | + Removes the given vertex from the graph and deletes all incoming and outgoing edges from |
| 74 | + the given vertex as well. If the given vertex does not exist, a ValueError will be thrown. |
| 75 | + """ |
| 76 | + if self.contains_vertex(vertex): |
| 77 | + if not self.directed: |
| 78 | + # If not directed, find all neighboring vertices and delete all references of |
| 79 | + # edges connecting to the given vertex |
| 80 | + for neighbor in self.adj_list[vertex]: |
| 81 | + self.adj_list[neighbor].remove(vertex) |
143 | 82 | else:
|
144 |
| - self.adj_list[source_vertex] = [destination_vertex] |
145 |
| - self.adj_list[destination_vertex] = [] |
| 83 | + # If directed, search all neighbors of all vertices and delete all references of |
| 84 | + # edges connecting to the given vertex |
| 85 | + for edge_list in self.adj_list.values(): |
| 86 | + if vertex in edge_list: |
| 87 | + edge_list.remove(vertex) |
146 | 88 |
|
147 |
| - return self |
| 89 | + # Finally, delete the given vertex and all of its outgoing edge references |
| 90 | + self.adj_list.pop(vertex) |
| 91 | + else: |
| 92 | + raise ValueError(f"Incorrect input: {vertex} does not exist in this graph.") |
| 93 | + |
| 94 | + def remove_edge( |
| 95 | + self, source_vertex: T, destination_vertex: T |
| 96 | + ) -> GraphAdjacencyList[T]: |
| 97 | + """ |
| 98 | + Removes the edge between the two vertices. If any given vertex doesn't exist |
| 99 | + or the edge does not exist, a ValueError will be thrown. |
| 100 | + """ |
| 101 | + if ( |
| 102 | + self.contains_vertex(source_vertex) |
| 103 | + and self.contains_vertex(destination_vertex) |
| 104 | + and self.contains_edge(source_vertex, destination_vertex) |
| 105 | + ): |
| 106 | + self.adj_list[source_vertex].remove(destination_vertex) |
| 107 | + if not self.directed: |
| 108 | + self.adj_list[destination_vertex].remove(source_vertex) |
| 109 | + else: |
| 110 | + raise ValueError( |
| 111 | + f"Incorrect input: Either {source_vertex} or {destination_vertex} do not exist \ |
| 112 | + OR the requested edge does not exists between them." |
| 113 | + ) |
| 114 | + |
| 115 | + def contains_vertex(self, vertex: T) -> bool: |
| 116 | + """ |
| 117 | + Returns True if the graph contains the vertex, False otherwise. |
| 118 | + """ |
| 119 | + return vertex in self.adj_list |
| 120 | + |
| 121 | + def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool: |
| 122 | + """ |
| 123 | + Returns True if the graph contains the edge from the source_vertex to the |
| 124 | + destination_vertex, False otherwise. If any given vertex doesn't exist, a |
| 125 | + ValueError will be thrown. |
| 126 | + """ |
| 127 | + if self.contains_vertex(source_vertex) and self.contains_vertex( |
| 128 | + destination_vertex |
| 129 | + ): |
| 130 | + return True if destination_vertex in self.adj_list[source_vertex] else False |
| 131 | + else: |
| 132 | + raise ValueError( |
| 133 | + f"Incorrect input: Either {source_vertex} or {destination_vertex} does not exist." |
| 134 | + ) |
| 135 | + |
| 136 | + def clear_graph(self) -> None: |
| 137 | + self.adj_list: dict[T, list[T]] = {} |
148 | 138 |
|
149 | 139 | def __repr__(self) -> str:
|
150 | 140 | return pformat(self.adj_list)
|
0 commit comments