Skip to content

Commit f520fb9

Browse files
committed
Improved Graph Implementations
Provides new implementation for graph_list.py and graph_matrix.py along with pytest suites for each. Fixes TheAlgorithms#8709
1 parent 793e564 commit f520fb9

File tree

4 files changed

+1055
-141
lines changed

4 files changed

+1055
-141
lines changed

Diff for: graphs/graph_list.py

+107-117
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env python3
22

3-
# Author: OMKAR PATHAK, Nwachukwu Chidiebere
3+
# Original Authors: OMKAR PATHAK, Nwachukwu Chidiebere
4+
# Redesigned and reimplemented by Vikram Nithyanandam
45

5-
# Use a Python dictionary to construct the graph.
6+
# Use an adjacency list via Python dictionary to construct the graph.
67
from __future__ import annotations
78

89
from pprint import pformat
@@ -14,137 +15,126 @@
1415
class GraphAdjacencyList(Generic[T]):
1516
"""
1617
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.
7319
"""
7420

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:
7624
"""
7725
Parameters:
7826
directed: (bool) Indicates if graph is directed or undirected. Default is True.
7927
"""
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
8229
self.directed = directed
8330

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+
8449
def add_edge(
8550
self, source_vertex: T, destination_vertex: T
8651
) -> GraphAdjacencyList[T]:
8752
"""
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.
9155
"""
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:
10063
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)
14382
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)
14688

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]] = {}
148138

149139
def __repr__(self) -> str:
150140
return pformat(self.adj_list)

0 commit comments

Comments
 (0)