import pandas as pd
import numpy as np
## GRAPHS / TREES
# Matrix Understanding First
# Using List of List
# Simple but lack advanced mathematical operations
def matrix ():
# Using List of List
matrixls = [[1,2,3],[4,5,6],[6,7,8]]
results=matrixls.copy()
# Numpy provide advanced mathematical operations
matrixnp= np.array([[1,2,2],[56,78,98],[12,32,96]])
results=matrixnp.copy()
# Using Pandas
matrixpd=pd.DataFrame([[1,2,3],[23,45,67],[34,565,677]])
results=matrixpd.copy()
# Common Matrix operations
A= np.array([[1,2],[45,65]])
B= np.array([[12,36],[36,25]])
results=A-B #Subtraction
results= A*B #Multiplication
results= np.dot(A,B) # Dot Multiplication
results= A.T # Transpose
results= np.linalg.inv(A) # Inverse
# What about Matrix Representations in Graph Data Structure
#Graphs can be represented as adjacency matrices,
#and matrix multiplication helps us count paths of different lengths between
nodes.
graph = np.array([[0,1,1],[1,0,1],[1,1,0]])
#DEgreen of a Node is the sum of its row values in the adjacent matrix
degree=np.sum(graph, axis=1) # degree
# Path Counting
# The Square of Adjacent Matrix gives the number of paths of length 2 between
nodes
#For 2D arrays (matrices):
#Both np.dot(A, A) and np.matmul(A, A) work similarly,
# but np.matmul() is clearer and more explicit in its intent.
#For 1D arrays (vectors):
#np.dot() will calculate the dot product,
#but np.matmul() will return the same result for 1D arrays as np.dot().
# The degree of a node is simply the count of edges connected to it.
# In the adjacency matrix, each row corresponds to a node, and the sum of the
row values gives the degree.
# Higher degree means the node is more connected
#The n-th power of the adjacency matrix gives us information about paths of
length n.
paths_2= np.matmul(graph,graph) # Numper of paths of length2
paths_3= np.matmul(paths_2, graph)
results=paths_3.copy()
#### USING A DICTIONARY..
results= {1: "keys", 2:"laptop",4:"jug"}
dict1= {"name": "James","age": 46 , "City": "Ikosi"}
# Accessing Values in DICTIONARY
results= dict1["age"] # accesing age
results= dict1["name"] # Accessing Name
# Lets say we want to add items
dict1["country"]="USA" #adding country
results=dict1
# Lets say we want to update
dict1["age"]=31
dict1["name"]="Hakeem"
results=dict1
# Lets say we want to remove itemss
del dict1["country"]
results=dict1
#Checking if a Key Exist
results = "name" in dict1
results= "age" not in dict1
# Looping through Keys and Values
keys=[]
values2=[]
bd = {} # Initialize an empty dictionary
for key, values in dict1.items():
if key not in bd: # Ensure the key exists in bd
bd[key] = [] # Initialize it as an empty list
bd[key].append(values) # Append the values
results = bd # Store the results
# Representing graphs as a dictionary
#Each key in the dictionary represents a node, and the corresponding value is
another dictionary or list that #contains the neighbors (and possibly the weights)
of that node.
# (A)---(B)
# | /
# | /
# (C)
graph2= {"A": ["B","C"], "B":["A","C"],"C":["A","B"]}
results= graph2
#(A) Finding the Degree of Each Node
#The degree of a node is the number of edges connected to it.
#In a dictionary, this can be calculated by counting the number of neighbors
(keys) for each node.
results=[]
for node, neighbors in graph2.items():
results.append(f" Degree of Node {node} : {len(neighbors)}")
# Checking if two nodes are connected
# to check if two nodes are connected or if there is an edge between two nodes
# we simply check if one nodes occur in the list of nodes of the other
if 'B' in graph2["A"]:
results="There is an edge between A and B"
else:
results="No edge between A and B"
## Adding an Edge between two nodes
## To an edge between node A and D
graph2["A"].append("D") #.... append
graph2["D"]=["A"] ##... then add
results=graph2
# Removing an edge between A and B
graph2["A"]. remove ("B")
graph2['B'].remove("A")
results=graph2
# Finding All paths of length2
# if you want to find all paths of length2(i,e two edges), you can iterate over
each neighbors
# and find the neighbors of those neighbors
# find all paths of length2
graph2 = {
"A": ["B", "D"],
"B": ["A", "C", "D"],
"C": ["B"],
"D": ["A", "B"]
}
results=[]
for node, neighbors in graph2.items(): # This goes through each nodes ,
neighbors stores all neighbors
for neighbor in neighbors: #This looks at all directly connected
neighbors
for second_neighbor in graph2[neighbor]: #this looks at the neighbor
of the first neighbors
# If we started at A and went to B and then went back to A we dont count that
if second_neighbor !=node:
#print (f"path from {node} to {second_neighbor}")
results.append(f"path from {node} to {second_neighbor}")
#### GRAPHS IN DETAILS
results=[]
for node, neighbor1 in graph2.items():
results.append(f"{node} --> {neighbor1}")
#Traversing a Graphs
# What is Graph Traversal
# Graph Traversal is the process of visiting all nodes of a graph in a
systematic way
# It helps to:
# Find connections between nodes
# Search for specific elements
# Solve complex problems path finding problems
# Two main methods
# 1; Depth First Search (DFS): Explore one path as deep as possible before
backtracking
# 2; Breadth First Search (BFS): Explore all neighbor of a node before moving
to the next nodes
# DEPT FIRST SEARCh:
print( f"{results}")
matrix()
graphr = {
"A": ["B", "D"],
"B": ["A", "C", "D"],
"C": ["B"],
"D": ["A", "B"]
}
# DEPT FIRST SEARCh:
def dfs(graphs, node, visited=None):
if visited is None:
visited=set() # Create an empty set to track the visited Node
visited.add(node) # Make the current node as visited
print(node, end=" ") # Ensure nodes are pretend in one line # Process the
nodes
# Visit all unvisited nighbors
for neighbor in graphs[node]:
if neighbor not in visited:
dfs(graphs, neighbor, visited)
# Perform DFS Starting from node A
dfs(graphr,"A")
graphr = {
"A": ["B", "D"],
# "B": ["A", "C", "D"],
"C": ["B"],
"D": ["A", "B"]
}
def dfs(graphsr, node, visited=None):
if visited is None:
visited= set() # Initialize visited to keep counts of nodes
visited
if node not in visited: # Check if node is not being visited already
print(node, end=" ") # process the node
visited.add(node) # Add the node to visted
for neighbor in graphsr[node]: # For each of the neighbor in the node
dfs(graphr,node, visited) # Apply the DfS model
dfs(graphr, "A")
#def say_hello():
# print('Hello, World')
#for i in range(5):
# say_hello()