This project must be completed individually. We want to evaluate you on the basis of what you can do by consulting the resources listed below, and without help from anyone other than course staff. Seeking or giving aid on this assignment is prohibited (except for asking course staff); doing so constitutes academic misconduct which can have serious consequences. If you are unsure about whether something is allowed, ask. The course syllabus contains more information about the course and university policies regarding academic honesty.
In this project you'll build a module that is similar to the work we did in trees.py and integerset.py, and which implements a partial replacement for Python's dict type using a binary search tree as its backing data structure.
Ask if you are unsure whether a resource falls under one of these categories, or if you think I missed an essential resource.
Contact the instructor or your TA by email, in office hours, or on discord.
Read this section to quickly get an idea of what the project is about. But understand that the precise specifications in the next section must also be read.
set and IntegerSet¶In lectures 16-18 of MCS 275, we built classes for binary trees, binary search trees (BST), and then we created a class IntegerSet that is similar to Python's built-in set type (but which has fewer features and additional restrictions on elements). The final structure of that work was:
trees.py:Node representing a binary tree where each node stores a keyBST (a subclass of Node) representing a possibly empty BST supporting insertion and search.integerset.py:IntegerSet for storing an unordered collection of distinct integers, which uses a BST as its main data structure.You don't need to know anything about Node or BST to use IntegerSet. The use of trees is entirely "behind the scenes".
If you have code that uses Python's set type, then you can use IntegerSet as a drop-in replacement as long as:
set class are add(...) and __contains__(...) (through the in operator)dict and Dictionary¶In this project you will do something similar for Python's dict type. That is, you will build a partial replacement called Dictionary that uses a variation on a BST for storage. It will have fewer methods and more restrictions than Python's dict type.
When you're done, you'll have the following classes and module:
proj3.py:KVNode representing a binary tree where each node stores two values: a key and a value.KVBST (a subclass of KVNode) representing a possibly empty BST that supports searching by key and various other operations.Dictionary for storing an unordered collection of key-value pairs, which uses a KVBST as its main data structure.When you're done, you will have a class Dictionary that would serve as a drop-in replacement for Python's dict in any program that:
>, <, and == (e.g. all integers, or all strings)In addition to creating these classes, the project specification below also asks you to write some test code for the new Dictionary class.
Before we go into the details of the code you need to write, I want to emphasize a few things about the relation between this project and our previous work in the lecture examples:
trees and integerset).trees.py and integerset.py for this project. Just make sure anything you adapt it is truly and fully updated, with no inaccurate comments, no parts that are irrelevant or unused, etc..trees or integerset modules. This is about making a bunch of new classes that have some similarities to the ones in those modules, but are different enough that importing is not helpful.dict to store any key-value pairs in this project. The whole point is to build a partial replacement for dict from scratch. Similarly, when making IntegerSet we didn't use Python's set type.Dictionary does not impose any type restrictions directly. Unlike IntegerSet, this class doesn't require integer keys, and in general shouldn't do any checking of types.proj3 module you must write¶Download the starter pack:
It contains a file proj3.py with three empty class definitions and a placeholder where module test code would go. Edit that file and submit it as your project. Make it so that the contents follow the specifications in this section.
KVNode¶Represents a binary tree with left child, right child, and parent attributes, as well as data attributes key and value at each node. The main difference from the Node class from lecture is the presence of the additional value attribute.
__init__(self,key=None, value=None, left=None, right=None, parent=None) - Stores all the arguments as instance attributes. (It is expected that left, right, and parent are either None or are instances of KVNode.)All initialized from the constructor arguments.
leftrightparentkeyvaluekeys(self) - Returns a list of key attributes seen during an inorder traversal of the tree.values(self) - Returns a list of value attributes seen during an inorder traversal of the tree.items(self) - Returns a list of (key,value) pairs seen during an inorder traversal of the tree.nodes(self) - Returns a list of the node objects seen during an inorder traversal of the tree.__str__(self) - Returns the a string like <57:'onion'> where 57 is replaced by repr() applied to the key attribute and 'onion' by repr() applied to the value attribute of this node.__repr__(self) - Returns the same thing as __str__.KVBST, a subclass of KVNode¶Represents a binary search tree in which a collection of key-value pairs are ordered by key. That is, any key in the left subtree is less than or equal to the key of this node, and any key in the right subtree is greater than the key of this node. In particular every key is expected to be comparable to all of the other keys in this tree (though there is no explicit checking for this).
The value attributes are present at each node but we don't care about their types, how they compare to one another, or even whether they can be compared.
As a special case, if an instance haskey attribute equal to None then the binary search tree is considered to be empty, and the first key-value pair that is inserted will live in this node.
KVNode, not changed.KVNode, not changed.insert(self,k,v) - Find a suitable place to add a KVBST node to this tree that has key k and value v, maintaining the search tree property.search(self,k) - Find a node in this tree that has key equal to k. If no such node exists, return None. Otherwise, return the KVBST object of the node that was located.Dictionary (not a subclass of anything)¶Represents a partial replacement for dict that uses a KVBST to store its data. Supports assigning values to keys, retrieving values by key, and checking whether a key is present. All keys must be comparable to one another.
__init__(self,starting_items=None) - Creates a new empty KVBST and stores as an instance attribute T. If starting_items is not None, assume it is a dict object or something that behaves similarly (can iterate over it and get keys; can index it to get values; has a .items() method). Any key-value pair present in starting_items is added to this Dictionary. This provides a way to instantiate Dictionary and have it immediately populated with keys and values, rather than starting empty.T - an instance of KVBST that is used to store the key-value pairs. Is private, i.e. meant to be used by methods of this class but not accessed directly by users of the class.__contains(self,k)__ - If there is a node in T with key k, return True. Otherwise, return False.
"foo" in D, if D is a Dictionary instance. That would translate to method call D.__contains__("foo").__getitem__(self,k)__ - If there is a node in T with key k, return the value attribute of that node. Otherwise, raise a KeyError.
D["foo"], if D is a Dictionary instance. That would translate to method call D.__getitem__("foo").__setitem__(self,k,v)__ - If there is a node in T with key k, change its value attribute to v. If there is no such node, add one that has key k and value v.
D["foo"] = 47, if D is a Dictionary instance. That would translate to method call D.__setitem__("foo",47).__str__(self) - Return a string of this formDictionary({5: 'hello', 11: 'goodbye', 29: False, 58: 8619215})
{} characters is a series of comma-separated key: value pairs with keys in increasing order. Each key and value should be shown as the string returned by repr() applied to that attribute.__repr__(self) - Returns the same thing as __str__.In addition to defining the classes, you need to write some code that tests and demonstrates the behavior of Dictionary.
But that test code must not run when proj3 is imported as a module.
The starter pack has a section that looks like this:
if __name__=="__main__":
# PUT TEST CODE HERE
pass
This conditional checks whether proj3.py is being run as a script (e.g. with python3 proj3.py). If it is, the code inside the conditional runs. So that's where the test code will go.
Specifically, add statements that demonstrate and test the Dictionary class as follows:
DictionaryDictionaryDictionary for the values associated with several keys, all of which are present. Print the key-value pairs.Dictionary for the values associated with several keys, none of which are present. That should raise an exception; make sure it does, and catch it using a try: ... except: block so it doesn't interrupt the test code.Dictionary, assign a value to a key in that Dictionary, then give the same key a new value. Print the Dictionary after each step.Dictionary and add 10,000 key-value pairs, where the keys and values are integers randomly selected from the range 0 to 999,999 (inclusive). Print a message like "Adding 10000 key-value pairs" beforehand, and "Done" afterward, but don't print anything during the process.All such test code needs to be in one big indented block under if __name__=="__main__":.
This test code should mean that running
python3 proj3.py
in the terminal will provide a nice quick test of the Dictionary class, but opening the Python REPL and running
import proj3
will not result in any test code running.
If you want to put code that tests the KVNode and KVBST classes, you can do so. However, please put such tests inside the same conditional and before the Dictionary tests.
Your project must follow the rules in the MCS 275 coding standards document. In addition:
proj3.py.proj3.py call one another if/when it is appropriate, rather than needlessly duplicating code.proj3.py except those in Python's standard library, as needed.dict to store key-value pairs in class Dictionary. Use a KVBST instead.proj3.py must not do anything other than define classes.KVNode, KVBST, and Dictionary¶This section contains some cells of Python code that use a solution to Project 3.
from proj3 import KVNode, KVBST, Dictionary
# Create a node
a = KVNode(key=57,value="onion")
a
# Create another node
b = KVNode(key="The ultimate volleyball/programming vacation weekend", value=191458)
b
# Make one node a child of the other
# Their keys are not comparable, but that's OK for `KVNode`
a.left = b
b.parent = a
# Now we should have
# a
# / \
# b None
# / \
# None None
print(a)
print("\t",a.left)
print("\t",a.right)
print("-"*80)
print(b)
print("\t",b.left)
print("\t",b.right)
# Make an empty KVBST
T = KVBST()
T
# Add some key/value pairs; these have string keys, and strings are comparable, so it's OK
T.insert("kitten",88)
T.insert("chinchilla",75)
T.insert("mole rat",204)
# Notice the left child has alphabetically earlier key
# and the right child has alphabetically later key
print(T)
print("\t",T.left)
print("\t",T.right)
# No other children present
assert T.left.left == None
assert T.left.right == None
assert T.right.left == None
assert T.right.right == None
# Check parent relations
assert T.right.parent == T
assert T.left.parent == T
# Make an empty dictionary
D = Dictionary()
D
# Add some stuff
D[5] = "haberdasher"
D[9] = "oxygen"
D[2] = "priority"
D
# Change some stuff
D[9] = "argon" # modify
D[11] = "transformers lunchbox" # add
D
# What's the root node of the underlying tree?
D.T
# Check membership as key
8 in D
# Check membership as key
9 in D
# Retrieve value associated to key
D[2]
# Attempt to retrieve value for key that isn't there
try:
D[88461713]
except Exception as e:
print("That generated an exception of type",e.__class__.__name__)
# Attempt to get a key that isn't comparable to the ones already there
# will generate an exception because comparison is attempted
# (I catch the exception to avoid printing the solution code
# as part of the traceback!)
try:
D["reconsidering"] # str not comparable to int
except Exception as e:
print("That generated an exception of type",e.__class__.__name__)
# Make a Dictionary that starts with some items in it
# by passing `starting_items`
E = Dictionary({3:17, 2:25, 6:0, 5:0, 1:42})
E
E[2]
try:
E[4]
except Exception as e:
print("That generated an exception of type",e.__class__.__name__)
E[2]=10000000000
E[2]
The autograder tests your module and grades it based on its behavior. The test code in proj3.py won't be tested by the autograder.
I will review your code and look for adherence to the coding standards and other rules given above. I will also check that the test code in proj3.py does what was specified above.