Files
CSC111/practice/prep8.py
T
2022-03-06 19:46:34 -05:00

255 lines
8.3 KiB
Python

"""CSC111 Winter 2022 Prep 8: Programming Exercises
Instructions (READ THIS FIRST!)
===============================
This module contains the graph implementation we studied in lecture, with a few
additional methods for you to implement on this exercise.
We have marked each place you need to write code with the word "TODO".
As you complete your work in this file, delete each TODO comment.
You may add additional doctests, but they will not be graded. You should test your work
carefully before submitting it!
Copyright and Usage Information
===============================
This file is provided solely for the personal and private use of students
taking CSC111 at the University of Toronto St. George campus. All forms of
distribution of this code, whether as given or with any changes, are
expressly prohibited. For more information on copyright for CSC111 materials,
please consult our Course Syllabus.
This file is Copyright (c) 2022 Mario Badr and David Liu.
"""
from __future__ import annotations
from typing import Any
class Graph:
"""A graph.
"""
# Private Instance Attributes:
# - _vertices:
# A collection of the vertices contained in this graph.
# Maps item to _Vertex object.
_vertices: dict[Any, _Vertex]
def __init__(self) -> None:
"""Initialize an empty graph (no vertices or edges)."""
self._vertices = {}
def add_vertex(self, item: Any) -> None:
"""Add a vertex with the given item to this graph.
The new vertex is not adjacent to any other vertices.
"""
self._vertices[item] = _Vertex(item, set())
def add_edge(self, item1: Any, item2: Any) -> None:
"""Add an edge between the two vertices with the given items in this graph.
Raise a ValueError if item1 or item2 do not appear as vertices in this graph.
Preconditions:
- item1 != item2
"""
if item1 in self._vertices and item2 in self._vertices:
v1 = self._vertices[item1]
v2 = self._vertices[item2]
# Add the new edge
v1.neighbours.add(v2)
v2.neighbours.add(v1)
else:
# We didn't find an existing vertex for both items.
raise ValueError
def connected(self, item1: Any, item2: Any) -> bool:
"""Return whether item1 and item2 are connected vertices in this graph.
Return False if item1 or item2 do not appear as vertices in this graph.
>>> g = Graph()
>>> g.add_vertex(1)
>>> g.add_vertex(2)
>>> g.add_vertex(3)
>>> g.add_vertex(4)
>>> g.add_edge(1, 2)
>>> g.add_edge(2, 3)
>>> g.connected(1, 3)
True
>>> g.connected(1, 4)
False
"""
if item1 in self._vertices and item2 in self._vertices:
v1 = self._vertices[item1]
return v1.check_connected(item2, set()) # Pass in an empty "visited" set
else:
return False
def get_connected_component(self, item: Any) -> set:
"""Return a set of all ITEMS connected to the given item in this graph.
Raise a ValueError if item does not appears as a vertex in this graph.
>>> g = Graph()
>>> for i in range(0, 5):
... g.add_vertex(i)
>>> g.add_edge(0, 1)
>>> g.add_edge(1, 2)
>>> g.add_edge(1, 3)
>>> g.add_edge(2, 3)
>>> g.get_connected_component(0) == {0, 1, 2, 3}
True
Note: we've implemented this method for you, and you should not change it.
Instead, your task is to implement _Vertex.get_connected_component below.
"""
if item not in self._vertices:
raise ValueError
else:
return self._vertices[item].get_connected_component(set())
def in_cycle(self, item: Any) -> bool:
"""Return whether the given item is in a cycle in this graph.
Return False if item does not appears as a vertex in this graph.
KEY OBSERVATION. A vertex v is in a cycle if and only if:
v has two distinct neighbours u and w that are connected to each other
by a path that doesn't use v.
>>> g = Graph()
>>> for i in range(0, 5):
... g.add_vertex(i)
>>> g.add_edge(0, 1)
>>> g.add_edge(1, 2)
>>> g.add_edge(1, 3)
>>> g.add_edge(2, 3)
>>> g.in_cycle(1)
True
>>> g.in_cycle(0)
False
>>> g.add_edge(4, 0)
>>> g.in_cycle(0)
False
Implementation notes:
1. This method should call _Vertex.check_connected (following the above
description).
2. Don't try to make this method recursive, or copy and paste the implementation
of _Vertex.check_connected! That's not necessary here.
"""
# Does not exist
if item not in self._vertices:
return False
v = self._vertices[item]
# Combinations
for u in v.neighbours:
for w in v.neighbours:
# Distinct combinations
if u == w or u == v or w == v:
continue
if u.check_connected(w.item, {v}):
return True
return False
class _Vertex:
"""A vertex in a graph.
Instance Attributes:
- item: The data stored in this vertex.
- neighbours: The vertices that are adjacent to this vertex.
Representation Invariants:
- self not in self.neighbours
- all(self in u.neighbours for u in self.neighbours)
"""
item: Any
neighbours: set[_Vertex]
def __init__(self, item: Any, neighbours: set[_Vertex]) -> None:
"""Initialize a new vertex with the given item and neighbours."""
self.item = item
self.neighbours = neighbours
def check_connected(self, target_item: Any, visited: set[_Vertex]) -> bool:
"""Return whether this vertex is connected to a vertex corresponding to the target_item,
WITHOUT using any of the vertices in visited.
Preconditions:
- self not in visited
"""
if self.item == target_item:
# Our base case: the target_item is the current vertex
return True
else:
visited.add(self) # Add self to the set of visited vertices
for u in self.neighbours:
if u not in visited: # Only recurse on vertices that haven't been visited
if u.check_connected(target_item, visited):
return True
return False
def get_connected_component(self, visited: set[_Vertex]) -> set:
"""Return a set of all ITEMS connected to self by a path that does not use
any vertices in visited.
The items of the vertices in visited CANNOT appear in the returned set.
Preconditions:
- self not in visited
Implementation notes:
1. This can be implemented in a similar way to _Vertex.check_connected.
2. This method must be recursive, and will have an implicit base case:
when all vertices in self.neighbours are already in visited.
3. Use a loop accumulator to store a set of the vertices connected to self.
>>> g = Graph()
>>> for i in range(0, 7):
... g.add_vertex(i)
>>> g.add_edge(0, 1)
>>> g.add_edge(1, 2)
>>> g.add_edge(1, 3)
>>> g.add_edge(2, 3)
>>> g.get_connected_component(1) == {0, 1, 2, 3}
True
>>> g.add_edge(4, 0)
>>> g.get_connected_component(0) == {0, 1, 2, 3, 4}
True
>>> g.get_connected_component(5)
{5}
>>> g._vertices[5].get_connected_component({g._vertices[5]})
set()
>>> g._vertices[6].get_connected_component(set())
{6}
"""
if self in visited:
return set()
visited.add(self)
nums = {self.item}
for u in self.neighbours:
nums = nums.union(u.get_connected_component(visited))
return nums
if __name__ == '__main__':
# Note: we are NOT using python_ta.contracts for this prep.
# (Feel free to ask why in office hours/Campuswire.)
import doctest
doctest.testmod()
import python_ta
python_ta.check_all(config={
'max-line-length': 100,
'disable': ['E1136'],
'max-nested-blocks': 4
})