Graphs

 
# SIMPLE GRAPHS
# -------------
# Concept:
#   - A graph is a data structure used to represent relationships.
#   - It is made of: vertices (nodes) and edges (relationships).
#
# Unline stacks of queue: 
#   - There is NO top or front
#   - connections can be many-to-many
#
# Real-life graphs: 
#   - social networks
#   - flight routes
#   - recommendations systems
# 
# Example: Friend relationships
#   - We use a dictionary
#   - Each key is a vertex
#   - Each list contains connected vertices
#   - Connections are biderectional
# -----------------------------------------

friends = {
    'Alice': ['Bob', 'Diana', 'Fred'],
    'Bob': ['Alice', 'Cynthia', 'Diana'],
    'Cynthia': ['Bob'],
    'Diana': ['Alice', 'Bob', 'Fred'],
    'Elise': ['Fred'],
    'Fred': ['Alice', 'Diana', 'Elise'],
}

print("Alice's friends:", friends['Alice'])
# -----------------------------------------
# Alice's friends: ['Bob', 'Diana', 'Fred']

Directed Graphs

 
# DIRECTED GRAPHS
# ---------------
# Relationships are not always mutual.
# In directed graphs connection is one-way.
#
# Example: Social medial follows:
#   - Alice follows Bob
#   - Bob does not have to follow Alice
# -------------------------------------

followees = {}

followees['Alice'] = ['Bob', 'Diana']
followees['Bob'] = ['Cynthia']
followees['Cynthia'] = ['Bob']

assert 'Bob' in followees['Alice']
assert 'Alice' not in followees['Bob']

print("Bob is following Alice")
print("Alice is not following Bob")
# ---------------------------------
# Bob is following Alice
# Alice is not following Bob

Object Oriented Graphs

 
# OBJECT ORIENTED GRAPHS
# ----------------------
# Dictionary are great for simple graphs.
#
# Sometimes we need objects that provide us:
#   - behavior
#   - encapsulation
#   - clear relationships
#
# Example: 
#   - Friend relationships (mutual)
# ---------------------------------

class Vertex:
    def __init__(self, name):
        self.value = name
        self.adjacent_vertices = []  # LIST

    def add_adjacent(self, vertex):
        # Add connection
        self.adjacent_vertices.append(vertex)

        # Ensure mutual relationship
        if self not in vertex.adjacent_vertices:
            vertex.adjacent_vertices.append(self)

a = Vertex("Alice")
b = Vertex("Bob")
c = Vertex("Cynthia")

a.add_adjacent(b)
a.add_adjacent(c)
b.add_adjacent(c)

print("A neighbours:", [v.value for v in a.adjacent_vertices])
print("B neighbours:", [v.value for v in b.adjacent_vertices])

# ----------------------------------
# A neighbours: ['Bob', 'Cynthia']
# B neighbours: ['Alice', 'Cynthia']

Weighted Graphs

 
# WEIGHTED GRAPHS
#----------------
# Concept:
#   - In some graphs, connections have values:
#     (distance, cost, time, price)
#
# Example:
#   - Flights between cities
#
# Vertices:
#   - Dallas -> Toronto costs $138
#   - Toronto -> Dalls costs $216
#
# Direction and cost both matters
# -------------------------------

class WeightedVertex:
    def __init__(self, value):
        self.value = value
        self.adjacent_vertices = {}  # DICT

    def add_adjacent(self, vertex, price):
        self.adjacent_vertices[vertex.value] = price

d = WeightedVertex('Dallas')
t = WeightedVertex('Toronto')

d.add_adjacent(t, 138)
t.add_adjacent(d, 216)

print('Dallas flights:', d.adjacent_vertices)
print('Toronto flights:', t.adjacent_vertices)
# --------------------------------------------
# Dallas flights: {'Toronto': 138}
# Toronto flights: {'Dallas': 216}




References: