Graph API Interface

This tutorial provides a complete guide to the Graph API interface, covering initialization methods, edge operations, graph construction, and utility functions.

Overview

The Graph class represents an unweighted graph using an adjacency list representation. It supports flexible initialization, efficient operations, and built-in validation for safe usage.

Note

Nodes are indexed from 0 to V-1 and the graph can be either directed or undirected.

Initialization

The Graph class supports multiple initialization styles.

1. Initialize with number of vertices

Create an empty graph with a fixed number of vertices.

from paragon import Graph

g = Graph(vertices=5)
print(g)

Output:

Graph(vertices=5, directed=False)

2. Initialize with edges

Create a graph and initialize it with edges.

from paragon import Graph
g = Graph(
    vertices=3,
    edges=[(0, 1), (1, 2)]
)

print(g.get_adj())

Output:

[[1], [0, 2], [1]]

3. Directed graph

Create a directed graph.

from paragon import Graph
g = Graph(vertices=3, directed=True)

g.add_edge(u=0, v=1)
print(g.get_adj())

Output:

[[1], [], []]

Warning

  • Passing invalid types (e.g., strings instead of integers) raises TypeError

  • Invalid indices (out-of-range) raise ValueError

Edge Operations

add_edge(u, v)

Adds an edge between vertices u and v.

from paragon import Graph
g = Graph(vertices=3)

g.add_edge(u=0, v=1)
g.add_edge(u=1, v=2)

print(g.get_adj())

Output:

[[1], [0, 2], [1]]

Parameters:

  • u : int — source vertex

  • v : int — destination vertex

Warning

  • Negative indices are not allowed

  • Out-of-range indices raise ValueError

  • Non-integer inputs raise TypeError

add_edges(edges)

Adds multiple edges at once.

from paragon import Graph
g = Graph(vertices=4)

g.add_edges(edges=[
    (0, 1),
    (1, 2),
    (2, 3)
])

print(g.get_adj())

Output:

[[1], [0, 2], [1, 3], [2]]

add_vertex()

Adds a new vertex to the graph.

from paragon import Graph
g = Graph(vertices=3)

g.add_vertex()
print(g.vertices())

Output:

4

Note

The new vertex is added without edges.

Graph Construction

build_from_adj_matrix(matrix)

Build graph using an adjacency matrix.

from paragon import Graph
g = Graph(vertices=3)

matrix = [
    [0, 1, 0],
    [1, 0, 1],
    [0, 1, 0]
]

g.build_from_adj_matrix(matrix=matrix)
print(g.get_adj())

Output:

[[1], [0, 2], [1]]

build_from_adj_list(adjacency)

Build graph using adjacency list.

from paragon import Graph
g = Graph(vertices=3)

adjacency = [
    [1, 2],
    [0],
    [0]
]

g.build_from_adj_list(adjacency=adjacency)
print(g.get_adj())

Output:

[[1, 2], [0], [0]]

Tip

Use adjacency lists for better performance in sparse graphs.

Graph Information

vertices()

Returns number of vertices.

g = Graph(vertices=4)
print(g.vertices())

Output:

4

is_directed()

Check if graph is directed.

g = Graph(vertices=3, directed=True)
print(g.is_directed())

Output:

True

get_adj()

Returns adjacency list.

g = Graph(vertices=3)
g.add_edge(u=0, v=1)

print(g.get_adj())

Output:

[[1], [0], []]

degree(u)

Returns degree of vertex u.

g = Graph(vertices=3)
g.add_edges(edges=[(0, 1), (1, 2)])

print(g.degree(u=1))

Output:

2

has_edge(u, v)

Checks if edge exists.

g = Graph(vertices=3)
g.add_edge(u=0, v=1)

print(g.has_edge(u=0, v=1))
print(g.has_edge(u=1, v=2))

Output:

True
False

Warning

Passing invalid node indices will raise ValueError.

Debug Utilities

Representation & Dunder Methods

Python provides special methods (called dunder methods) that allow objects to behave like built-in types. The Graph class implements several of these methods to provide a clean and intuitive interface.

__repr__()

Returns a developer-friendly string representation of the graph.

g = Graph(vertices=5)
print(g)

Output:

Graph(vertices=5, edges=0, directed=False)

__len__()

Returns the number of vertices in the graph.

g = Graph(vertices=4)
print(len(g))

Output:

4

__contains__(node)

Checks whether a node exists in the graph.

g = Graph(vertices=3)

print(1 in g)
print(5 in g)

Output:

True
False

__getitem__(node)

Returns neighbors of a node.

g = Graph(vertices=3)
g.add_edges(edges=[(0, 1), (1, 2)])

print(g[1])

Output:

[0, 2]

__iter__()

Allows iteration over graph nodes.

g = Graph(vertices=3)

for node in g:
    print(node)

Output:

0
1
2

__eq__(other)

Checks if two graphs are equal.

g1 = Graph(vertices=3)
g2 = Graph(vertices=3)

print(g1 == g2)

Output:

True

__ne__(other)

Checks if two graphs are not equal.

print(g1 != g2)

Output:

False

__bool__()

Checks if graph is non-empty.

g = Graph(vertices=3)

if g:
    print("Graph is not empty")

Output:

Graph is not empty

__copy__() and __deepcopy__()

Create copies of the graph.

import copy

g = Graph(vertices=3)
g.add_edge(u=0, v=1)

g2 = copy.copy(g)
g3 = copy.deepcopy(g)

print(g2.get_adj())
print(g3.get_adj())

Output:

[[1], [0], []]
[[1], [0], []]

Note

copy.copy creates a shallow copy, while copy.deepcopy creates a fully independent copy of the graph.

Complete Dunder Methods Example

The following example demonstrates how all dunder methods work together.

from paragon import Graph
import copy

g = Graph(vertices=4)

g.add_edges(edges=[
    (0, 1),
    (1, 2),
    (2, 3)
])

# Length
print(len(g))

# Contains
print(2 in g)

# Indexing
print(g[1])

# Iteration
for node in g:
    print(node)

# String representation
print(g)

# Equality
g2 = copy.deepcopy(g)
print(g == g2)

Output:

4
True
[0, 2]
0
1
2
3
0: [1]
1: [0, 2]
2: [1, 3]
3: [2]
True

Tip

Dunder methods make your Graph class behave like native Python collections, improving readability and usability.

Complete Example

from paragon import Graph

g = Graph(vertices=4)

g.add_edges(edges=[
    (0, 1),
    (1, 2),
    (2, 3)
])

print("Vertices:", g.vertices())
print("Adjacency:", g.get_adj())
print("Degree of 1:", g.degree(u=1))
print("Has edge (0,1):", g.has_edge(u=0, v=1))

g.print_graph()

Output:

Vertices: 4
Adjacency: [[1], [0, 2], [1, 3], [2]]
Degree of 1: 2
Has edge (0,1): True
0 : 1
1 : 0 2
2 : 1 3
3 : 2

Best Practices

  • Use add_edges instead of multiple add_edge calls for efficiency

  • Prefer adjacency lists for large sparse graphs

  • Always validate input data before constructing graphs

  • Use len(graph) instead of graph.vertices() for cleaner code

  • Use graph[node] for quick adjacency access

  • Use node in graph for safe existence checks

  • Prefer copy.deepcopy when modifying graphs independently

Warning

Invalid node indices or malformed inputs will raise explicit errors. Ensure all vertices lie in the valid range [0, V-1].

Tip

Use directed graphs when modeling one-way relationships such as web links, dependencies, or workflows.