Skip to content

Commit

Permalink
Merge pull request #1 from shaunorpen/shaun-orpen
Browse files Browse the repository at this point in the history
WEBEU3 - Graphs - Shaun Orpen
  • Loading branch information
Oyekunle-Mark authored Mar 23, 2020
2 parents 33bdf59 + 6f7e8b1 commit df46f5c
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 20 deletions.
17 changes: 15 additions & 2 deletions projects/ancestor/ancestor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@

def earliest_ancestor(ancestors, starting_node):
pass
def get_parent(ancestors, starting_node):
for (parent, child) in ancestors:
if child == starting_node:
return parent

def earliest_ancestor(ancestors, starting_node, iterations = 0):
parent = get_parent(ancestors, starting_node)
if parent is None:
if iterations == 0:
return -1
else:
return starting_node
else:
iterations += 1
return earliest_ancestor(ancestors, parent, iterations)
149 changes: 133 additions & 16 deletions projects/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,59 @@ def add_vertex(self, vertex_id):
"""
Add a vertex to the graph.
"""
pass # TODO
self.vertices[vertex_id] = set()

def add_edge(self, v1, v2):
"""
Add a directed edge to the graph.
"""
pass # TODO
self.vertices[v1].add(v2)

def get_neighbors(self, vertex_id):
"""
Get all neighbors (edges) of a vertex.
"""
pass # TODO
return self.vertices[vertex_id]

def bft(self, starting_vertex):
"""
Print each vertex in breadth-first order
beginning from starting_vertex.
"""
pass # TODO
q = Queue()
q.enqueue(starting_vertex)

visited_vertices = list()

while q.size() > 0:
current_vertex = q.dequeue()
if current_vertex not in visited_vertices:
print(current_vertex)
visited_vertices.append(current_vertex)
for next_vertex in self.vertices[current_vertex]:
q.enqueue(next_vertex)

return visited_vertices

def dft(self, starting_vertex):
"""
Print each vertex in depth-first order
beginning from starting_vertex.
"""
pass # TODO
s = Stack()
s.push(starting_vertex)

visited_vertices = list()

while s.size() > 0:
current_vertex = s.pop()
if current_vertex not in visited_vertices:
print(current_vertex)
visited_vertices.append(current_vertex)
for next_vertex in self.vertices[current_vertex]:
s.push(next_vertex)

return visited_vertices

def dft_recursive(self, starting_vertex):
"""
Expand All @@ -48,33 +74,124 @@ def dft_recursive(self, starting_vertex):
This should be done using recursion.
"""
pass # TODO
s = Stack()
s.push(starting_vertex)

visited_vertices = list()

# while s.size() > 0:
# current_vertex = s.pop()
# if current_vertex not in visited_vertices:
# visited_vertices.append(current_vertex)
# for next_vertex in self.vertices[current_vertex]:
# s.push(next_vertex)

def recurse(vertex, stack, visited):
if stack.size() > 0:
current_vertex = stack.pop()
if current_vertex not in visited:
print(current_vertex)
visited.append(current_vertex)
for next_vertex in self.vertices[current_vertex]:
stack.push(next_vertex)
recurse(vertex, stack, visited)
else:
return -1

recurse(starting_vertex, s, visited_vertices)

return visited_vertices


def bfs(self, starting_vertex, destination_vertex):
"""
Return a list containing the shortest path from
starting_vertex to destination_vertex in
breath-first order.
"""
pass # TODO
q = Queue()
q.enqueue([starting_vertex])

visited = set()

while q.size() > 0:
path = q.dequeue()
last_vertex = path[-1]
if last_vertex not in visited:
if last_vertex == destination_vertex:
return path
else:
visited.add(last_vertex)
for next_vertex in self.vertices[last_vertex]:
new_path = [*path, next_vertex]
q.enqueue(new_path)


def dfs(self, starting_vertex, destination_vertex):
"""
Return a list containing a path from
starting_vertex to destination_vertex in
depth-first order.
"""
pass # TODO
s = Stack()
s.push([starting_vertex])

visited = set()

def dfs_recursive(self, starting_vertex):
while s.size() > 0:
path = s.pop()
last_vertex = path[-1]
if last_vertex not in visited:
if last_vertex == destination_vertex:
return path
else:
visited.add(last_vertex)
for next_vertex in self.vertices[last_vertex]:
new_path = [*path, next_vertex]
s.push(new_path)

def dfs_recursive(self, starting_vertex, destination_vertex):
"""
Return a list containing a path from
starting_vertex to destination_vertex in
depth-first order.
This should be done using recursion.
"""
pass # TODO
s = Stack()
s.push([starting_vertex])

visited = set()

# while s.size() > 0:
# path = s.pop()
# last_vertex = path[-1]
# if last_vertex not in visited:
# if last_vertex == destination_vertex:
# return path
# else:
# visited.add(last_vertex)
# for next_vertex in self.vertices[last_vertex]:
# new_path = [*path, next_vertex]
# s.push(new_path)

def recurse(start, end, stack, visited):
if stack.size() > 0:
path = stack.pop()
last_vertex = path[-1]
if last_vertex not in visited:
if last_vertex == end:
return path
else:
visited.add(last_vertex)
for next_vertex in self.vertices[last_vertex]:
new_path = [*path, next_vertex]
stack.push(new_path)
return recurse(start, end, stack, visited)
else:
return -1

return recurse(starting_vertex, destination_vertex, s, visited)

if __name__ == '__main__':
graph = Graph() # Instantiate your graph
Expand All @@ -101,7 +218,7 @@ def dfs_recursive(self, starting_vertex):
Should print:
{1: {2}, 2: {3, 4}, 3: {5}, 4: {6, 7}, 5: {3}, 6: {3}, 7: {1, 6}}
'''
print(graph.vertices)
# print(graph.vertices)

'''
Valid BFT paths:
Expand All @@ -118,7 +235,7 @@ def dfs_recursive(self, starting_vertex):
1, 2, 4, 3, 7, 6, 5
1, 2, 4, 3, 7, 5, 6
'''
graph.bft(1)
# print(graph.bft(1))

'''
Valid DFT paths:
Expand All @@ -127,19 +244,19 @@ def dfs_recursive(self, starting_vertex):
1, 2, 4, 7, 6, 3, 5
1, 2, 4, 6, 3, 5, 7
'''
graph.dft(1)
graph.dft_recursive(1)
# print(graph.dft(1))
# print(graph.dft_recursive(1))

'''
Valid BFS path:
[1, 2, 4, 6]
'''
print(graph.bfs(1, 6))
# print(graph.bfs(1, 6))

'''
Valid DFS paths:
[1, 2, 4, 6]
[1, 2, 4, 7, 6]
'''
print(graph.dfs(1, 6))
# print(graph.dfs(1, 6))
print(graph.dfs_recursive(1, 6))
15 changes: 15 additions & 0 deletions projects/social/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,28 @@ Note that in this sample, Users 3, 4 and 9 are not in User 1's extended social n

1. To create 100 users with an average of 10 friends each, how many times would you need to call `add_friendship()`? Why?

You'd need to call it 500 times because each friendship is bi-directional.

2. If you create 1000 users with an average of 5 random friends each, what percentage of other users will be in a particular user's extended social network? What is the average degree of separation between a user and those in his/her extended network?

> 99% of other users will be in a particular user's social network
The average degree of separation will be just over the average number of friendships

## 4. Stretch Goal

1. You might have found the results from question #2 above to be surprising. Would you expect results like this in real life? If not, what are some ways you could improve your friendship distribution model for more realistic results?

Yes, they were surprising. 99% coverage with only five friends each? Crazy. So, what's different to real life?

I think three things:
- Friendships in real life aren't random. They're built around where you live, work and socialise.
- Friendships in real life aren't all equal. Some people you see all the time. Others you see less often.
You're more likely to be friends with friends of friends you see all the time.
- Although there's obviously an average number of friends each of us has, the actual number of friends
any individual has varies widely. Some people know loads of people. Others keep to themselves.

Findind ways to add these factors to the distribution algorithm would make it more realistic.

2. If you followed the hints for part 1, your `populate_graph()` will run in O(n^2) time. Refactor your code to run in O(n) time. Are there any tradeoffs that come with this implementation?

58 changes: 56 additions & 2 deletions projects/social/social.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from util import Queue
import random
class User:
def __init__(self, name):
self.name = name
Expand Down Expand Up @@ -43,10 +45,36 @@ def populate_graph(self, num_users, avg_friendships):
self.users = {}
self.friendships = {}
# !!!! IMPLEMENT ME

# Add users
# iterate over 0 to num users...
for i in range(num_users):
# add user using an f-string
self.add_user(f'User {i}')

# Create friendships
# generate all possible friendship combinations
possible_friendships = list()
# avoid dups by making sure the first number is smaller than the second
# iterate over user id in users...
for user_id in self.users:
# iterate over friend id in in a range from user id + 1 to last id + 1...
for friend_id in range(user_id + 1, self.last_id + 1):
# append a user id and friend id tuple to the possible friendships
possible_friendships.append((user_id, friend_id))

# shuffle friendships random import
random.shuffle(possible_friendships)

# create friendships for the first N pairs of the list
# N is determined by the formula: num users * avg friendships // 2
# NOTE: need to divide by 2 since each add_friendship() creates 2 friendships
# iterate over a range using the formula as the end base...
for i in range (num_users * avg_friendships // 2):
# set friendship to possible friendships at index
(user_id, friend_id) = possible_friendships[i]
# add friendship of frienship[0], friendship[1]
self.add_friendship(user_id, friend_id)

def get_all_social_paths(self, user_id):
"""
Expand All @@ -59,12 +87,38 @@ def get_all_social_paths(self, user_id):
"""
visited = {} # Note that this is a dictionary, not a set
# !!!! IMPLEMENT ME
return visited

# create a queue
q = Queue()
# add the starting user_id to the queue as a list
q.enqueue([user_id])

while q.size() > 0:
path = q.dequeue()
if path[-1] not in visited:
visited[path[-1]] = path
for friend in self.friendships[path[-1]]:
q.enqueue([*path, friend])

return visited

if __name__ == '__main__':
sg = SocialGraph()
sg.populate_graph(10, 2)
print(sg.friendships)
connections = sg.get_all_social_paths(1)
print(connections)

# results = list()

# for i in range(10):
# sg = SocialGraph()
# sg.populate_graph(1000, 5)
# paths = sg.get_all_social_paths(1)
# total = 0
# for path in paths.keys():
# total += len(paths[path])
# results.append(total / len(paths.keys()))

# print(results)

28 changes: 28 additions & 0 deletions projects/social/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

# Note: This Queue class is sub-optimal. Why?
class Queue():
def __init__(self):
self.queue = []
def enqueue(self, value):
self.queue.append(value)
def dequeue(self):
if self.size() > 0:
return self.queue.pop(0)
else:
return None
def size(self):
return len(self.queue)

class Stack():
def __init__(self):
self.stack = []
def push(self, value):
self.stack.append(value)
def pop(self):
if self.size() > 0:
return self.stack.pop()
else:
return None
def size(self):
return len(self.stack)

0 comments on commit df46f5c

Please sign in to comment.