1. Starting the IDLE Python Interpreter
Simple Python InteractionsWhen you don't have any code written yet, this is simple. We want to start IDLE, Python's environment and editor.
Utilizing Support FilesLater, once you already have code written, you need to be careful how you start Python. Your programs typically won't be all in one file, and you need to start it in a way that it can find the related files. You'll need to do this each time you use Python, not just the first time.
You should see something like the window shown on the right. Note the version number shown in the first line. This is Python's interactive window. It is good for experimenting with small examples and running and testing your code. While you can write entire programs here, it's not a good tool for that. Of course, if you familiar with Python already and know other techniques for starting Python, you can still use those. Also, if you are using your own computer, you can also look into how to update PYTHONPATH, the list of folders/directories that Python searches for files. |
|
2. Python Documentation
As the course progresses, we will be using various features of Python. Most of the features you did not learn in COMP 140 will be discussed in lab. However, it is always useful to be able to look things up in the Python documentation. The Language Reference section includes documentation for any and all features of Python. The Library Reference includes documentation for the standard Python libraries. You will find useful information in the Library Reference about the built-in types and their operations.
We encourage you to consult the documentation when you are unsure of how something works in Python. Of course, you are also always welcome to discuss such issues with the staff, as well.
3. Sets
Quick Reference:
empty_set = set() # Create an empty set.
north_colleges = set(["Jones", "Brown", "Martel", "Duncan", "McMurtry"]) # Create a set with initial elements.
north_colleges.add("Nakhleh") # Add to a set.
north_colleges.remove("Nakhleh") # Remove from a set.
>>> "Baker" in north_colleges # Test for membership in a set.
False
Exercise 3.1
What would you expect the value of this expression to be?
>>> set([0,1,2]) == set([2,0,1,1])
What would you expect the result of this Python code to be?
>>> some_set = set([0,1,2])
>>> some_set.add(2)
>>> some_set
Exercise 3.2 - Union
Write a function
def union(set1, set2):
"""
Returns the union of two sets.
Arguments:
set1 -- The first set of the union.
set2 -- The second set of the union.
Returns:
A new set containing all the elements of set1 and set2.
"""
...
that takes two sets as arguments and returns their
union. (A union of two sets A and B is a new set that
contains all the elements of both A and B. For example, the
union of {1,2,3}
and {2,3,4}
is {1,2,3,4}
.)
Assert your function produces the following output:
>>> union(set([1,2,3]), set([4,5,6]))
set([1, 2, 3, 4, 5, 6])
>>> union(set([1,2,3]), set([2,3,4]))
set([1, 2, 3, 4])
>>> union(set(), set([3]))
set([3])
>>> union(set(), set())
set([])
This method already exists in the set
class
by the name of union
(along with many others,
type help(set)
for details). In the future,
feel free to use this method instead of implementing union
on your own. But do implement it yourself for this
exercise.
Exercise 3.3 - Intersection
Write a function
def intersection(set1, set2):
"""
Returns the intersection of two sets.
Arguments:
set1 -- The first set of the intersection.
set2 -- The second set of the intersection.
Returns:
A new set containing only the elements common to set1 and set2.
"""
...
that takes two sets as arguments and returns their intersection. (An intersection of two sets A and B is a new set that contains only the elements common to both A and B. For example, the intersection of {1,2,3} and {2,3,4} is {2,3}.)
Assert your function produces the following output:
>>> intersection(set([1,2,3]), set([2,3,4]))
set([2, 3])
>>> intersection(set([1,2]), set([3,4]))
set([])
>>> intersection(set(), set([3]))
set([])
More Information
Consult the Python documentation for more details about sets.
4. Dictionaries
Quick Reference:
empty_dict = { } # Create an empty dictionary.
bb_alumni_victory = {
2009:"Will Rice",
2008:"Brown",
2006:"Will Rice",
1992:"Hanszen",
1982:"Wiess" } # Create an explicit dictionary.
bb_alumni_victory[1984] = "Hanszen" # Add or update a key.
>>> bb_alumni_victory[2008]
'Brown'
Exercise 4.1 - Dictionary Creation
Write a function
def make_dict(keys, default_value):
"""
Creates a new dictionary with the specified keys and default value.
Arguments:
keys -- A list of keys to be included in the returned dictionary.
default_value -- The initial mapping value for each key.
Returns:
A dictionary where every key is mapped to the given default_value.
"""
...
that takes a list of keys and a default value and creates a dictionary containing these keys with each key mapping to the given default value.
Assert your function produces the following output:
>>> make_dict(["a","b","c"], "z")
{'a': 'z', 'c': 'z', 'b': 'z'}
>>> make_dict([], [])
{}
Exercise 4.2 - Value Assertion
Write a function
def ensure_key_value_pair(pairs, key, expected_value):
"""
Checks to ensure that the mapping of key in pairs matches the given expected value.
If the state of pairs is such that the given key is already mapped to the given expected value
this function in no way modifies the dictionary and returns the given dictionary.
If the state of pairs is such that the given key does not map to the given expected value
(or no such key is contained in pairs) then update (or add) the mapping of key to
the given expected value and return the given dictionary.
Arguments:
pairs -- A dictionary to check for the expected mapping.
key -- The key of the expected mapping.
expected_value -- The the value of the expected mapping.
Returns:
The given dictionary.
"""
...
that returns the given dictionary if the lookup value
of the dictionary under the given key
has
the same value as expected_value
. Otherwise,
ensure that the value at key
for the given
dictionary is expected_value
and return the
updated dictionary.
Note: You may find the dict
method
has_key(key)
which tests whether the given
key is contained in the dictionary useful.
Assert your function produces the following output:
>>> pairs = { 1:"a", 2:"b" }
>>> ensure_key_value_pair(pairs, 1, "a")
{1: 'a', 2: 'b'}
>>> ensure_key_value_pair(pairs, 2, "z")
{1: 'a', 2: 'z'}
>>> ensure_key_value_pair(pairs, 3, "x")
{1: 'a', 2: 'z', 3: 'x'}
More Information
Consult the Python documentation for more details about dictionaries.
5. Graphs
We will represent graphs as a dictionary utilizing nodes as keys and adjacency sets (i.e. the node's neighbors) as values. Therefore each node in the graph is a key in the dictionary that maps to a set containing that node's neighbors. For example the graph $G = (V, E)$ where $V = \{0, 1, 2\}$ and $E = \{ \{0, 1\}, \{1, 2\} \}$ can be represented as:
graph = { 0 : set([1]),
1 : set([0, 2]),
2 : set([1]) }
Exercise 5.1 - Node Count
Write a function:
def node_count(graph):
"""
Returns the number of nodes in a graph.
Arguments:
graph -- The given graph.
Returns:
The number of nodes in the given graph.
"""
...
that computes the number of nodes in a graph.
You can obtain a list of a given dictionary's keys by
using the keys()
method. For example:
graph = { ... }
nodes_in_graph = graph.keys() # A dict's keys define a graph's nodes!
Assert that your function produces the following output:
>>> graph1 = { 0: set(), 1: set()}
>>> node_count(graph1)
2
>>> graph2 = { }
>>> node_count(graph2)
0
Exercise 5.2 - Edge Count
Write a function:
def edge_count(graph):
"""
Returns the number of edges in a graph.
Arguments:
graph -- The given graph.
Returns:
The number of edges in the given graph.
"""
...
that computes the number of edges in a graph.
Assert that your function produces the following output:
>>> graph1 = { "0" : set(["1","2"]),
"1" : set(["0","2"]),
"2" : set(["1","0"]) }
>>> edge_count(graph1)
3
>>> graph2 = { 0: set(), 1: set()}
>>> edge_count(graph2)
0
>>> graph3 = { }
>>> edge_count(graph3)
0
Exercise 5.3 - Complete Graphs
Before starting this exercise copy the following code into the file you are working with:
def convert(g):
"""
Print an undirected graph in a form that can be used by the
graph visualizer.
Arguments:
g -- the given graph
Returns:
Nothing
"""
nodes = g.keys()
edges = []
for u in g:
for v in g[u]:
if not [v, u] in edges:
edges.append([u, v])
print [nodes, edges]
(Do not worry about what this function is doing.)
A complete graph is a graph where every unordered pair of distinct nodes is connected by an edge. Write a function
def make_complete_graph(num_nodes):
"""
Returns a complete graph containing num_nodes nodes.
The nodes of the returned graph will be 0...(num_nodes-1) if num_nodes-1 is positive.
An empty graph will be returned in all other cases.
Arguments:
num_nodes -- The number of nodes in the returned graph.
Returns:
A complete graph in dictionary form.
"""
...
that creates a complete graph with the specified
number of nodes. The nodes of the graph should be the
numbers from 0 to num_nodes-1
(for
num_nodes>0
). Now visually verify that
your function generates a complete graph by
interpreting:
>>> graph = make_complete_graph(5)
>>> convert(graph)
Cut and paste the output of convert
into the
textbox here
and click the “draw” button. Make sure that
you copy the text exactly. The graph visualizer does not
do any serious error checking and is not robust if your
input is incorrect.
Also assert your function produces the following output:
>>> graph = make_complete_graph(5)
>>> 3 in graph[3]
False
6. Digraphs
Recall that for digraphs (a.k.a. directed graphs) each
edge is oriented from a tail node to a head node. An edge
from node 0
to node 1
in a
digraph does not imply that an edge exists from
1
to 0
. In fact it is possible
for none, either or both such edges to
exist in a given digraph! We can modify our dictionary
representation of graphs to represent digraphs as
follows:
To represent an edge from node 0
to node
1
we place 1
in
0
's adjacency set but, unlike our graph
representation, we do not automatically place
0
in 1
's adjacency set. The
adjacency set for a given node key then records only
those nodes which are "pointed to" from the key. The
statement
if 1 in digraph[0]:
thus represents the question "Is there an edge from
node 0
to node 1
?" Because the
adjacency sets of 0
and 1
are
no longer symmetric we can now populate them
independently to represent only those nodes immediately
reachable from a given node.
Exercise 6.1 - Your First Digraph
Write the Python code to represent a digraph
containing the nodes V = {0, 1, 2, 3, 4, 5} where an edge
exists from n
to n+1
(for n =
0...4). Also include the edge (5,0). Show your code to a
lab assistant for verification.
Exercise 6.2 - In-Degree
Write a function
def in_degree(digraph, node):
"""
Computes the in-degree of the given node.
Arguments:
digraph -- a dictionary representation of a digraph.
node -- the given to node.
Returns:
The in-degree of node.
"""
...
to compute the in-degree for a node in a given digraph.
Assert your function produces the following output:
>>> digraph = { 1 : set([2]),
2 : set([3,4]),
3 : set([]),
4 : set([3,2]),
5 : set() }
>>> in_degree(digraph, 3)
2
>> in_degree(digraph, 4)
1
>> in_degree(digraph, 1)
0
7. Optional Challenge Exercise
These problems are not officially a part of lab. While you do not need to continue, as it's the first lab of the semester, there is obviously more to learn! Further, you may find this useful as you test your code.
Exercise 7.1 - Graph Validity
One interesting observation about our dictionary representation of graphs is that each edge in the graph is in essence recorded twice. For example in the case of a two node, one edge graph:
graph = { "0" : set(["1"]),
"1" : set(["0"]) }
we could test to see if an edge connects nodes
0
and 1
by either examining
node 0
's adjacency set looking for
1
or by examining
1
's adjacency set looking for
0
. If an edge connects these two nodes
together then it doesn't matter which of the two sets we
choose to examine--either will give us the answer. Node
0
's adjacency set containing 1
implies that 1
's set contains 0
and vice versa. Restated, if the answer to the question
"Is node 0 connected to node 1?" is "Yes" then the answer
to the question "Is node 1 connected to node 0?" must
also be "Yes." That is unless we have made a
mistake when creating the data structure
representation of our graph. For example, what if we
forgot to add node 0
to node
1
's adjacency set?
illogical_graph = { "0" : set(["1"]),
"1" : set([]) }
It would be useful to create a function to check our graph structures for such mistakes. As such, write a function
def is_undirected_graph_valid(graph):
"""
Tests whether the given graph is logically valid.
Asserts for every unordered pair of distinct nodes {n1, n2} that
if n2 appears in n1's adjacency set then n1 also appears inÂ
n2's adjacency set. Also asserts that no node appears inÂ
its own adjacency set and that every value that appears in
an adjacency set is a node in the graph.
Arguments:
graph -- The graph in dictionary form to test.
Returns:
True if the graph is logically valid. False otherwise.
"""
...
that tests whether a given graph is logically valid.
Assert your function produces the following output:
>>> graph1 = { "0" : set(["1","2"]),
"1" : set(["0","2"]),
"2" : set(["1","0"]) }
>>> is_undirected_graph_valid(graph1)
True
>>> graph2 = { "0" : set(["1","2"]),
"1" : set(["0","2"]),
"2" : set(["1"]) }
>>> is_undirected_graph_valid(graph2)
False
>>> graph3 = make_complete_graph(100)
>>> is_undirected_graph_valid(graph3)
True
>>> graph4 = { "0" : set(["1","2"]),
"1" : set(["0","2"]),
"2" : set(["1","3"]) }
>>> is_undirected_graph_valid(graph4)
False
>>> graph5 = { "0" : set(["0"]) }
>>> is_undirected_graph_valid(graph5)
False
It is good practice to routinely check the validity of your data structures as you test. You may find this function invaluable as you develop graph algorithms throughout the course.