CSE 130 - Programming Assignment #6

Python

140 points

Must be turned in no later than 4:59:59 PM on 03/09/2007
(see submission instructions below)

(click your browser's refresh button to ensure that you have the most recent version)

Note: To download and install Python version 2.5 on your home machines see this page. Remember that this is only to enable you to play with the assignment at home: The final version turned in must work on the ACS Linux machines. While you can use MacOS or Windows to begin working with Python, the code you turn in must be that required for the Linux environment.

Integrity of Scholarship

University rules on integrity of scholarship will be strictly enforced. By completing this assignment, you implicitly agree to abide by the UCSD Policy on Integrity of Scholarship described beginning on page 62 of the Academic Regulations section (PDF) of the 2002-2003 General Catalog, in particular, "all academic work will be done by the student to whom it is assigned, without unauthorized aid of any kind."

You are expected to do your own work on this assignment; there are no group projects in this course. You may (and are encouraged to) engage in general discussions with your classmates regarding the assignment, but specific details of a solution, including the solution itself, must always be your own work. Incidents that violate the University's rules on integrity of scholarship will be taken seriously: In addition to receiving a zero (0) on the assignment, students may also face other penalties, up to and including, expulsion from the University. Should you have any doubt about the moral and/or ethical implications of an activity associated with the completion of this assignment, please see the instructors.


Code Documentation and General Requirements

Code for all programming assignments should be well documented. A working program with no comments will receive only partial credit. Documentation entails providing documentation strings for all methods, classes, packages, etc., and comments throughout the code to explain the program logic. Comments in Python are preceded by # and extend to the end of the line. Documentation strings are strings in the first line of a function, method, etc., and are accessible using help(foo), where foo is the name of the method, class, etc. It is understood that some of the exercises in this programming assignment require extremely little code and will not require extensive comments.

While few programming assignments pretend to mimic the "real" world, they may, nevertheless, contain some of the ambiguity that exists outside the classroom. If, for example, an assignment is amenable to differing interpretations, such that more than one algorithm may implement a correct solution to the assignment, it is incumbent upon the programmer to document not only the functionality of the algorithm (and more broadly his/her interpretation of the program requirements), but to articulate clearly the reasoning behind a particular choice of solution.


Assignment Overview

The objective of this assignment is to introduce you to some more advanced features of Python. This assignment will cover topics from classes and OOP to higher order functions and the decorator pattern to infinite lists.

The assignment is spread over four python files misc.py, decorators.py, streams.py, and test.py, the text files, words and passwd, and the sample output file decorators.out that you need to download. These four files contain several skeleton Python functions and classes, with missing bodies or missing definitions, which currently contain the text raise Failure("to be written") or are present only as comments. Your task is to replace the text in those files with the the appropriate Python code for each of those expressions. An emphasis should be placed on writing concise easy to read code.

Assignment Testing and Evaluation

Your functions/programs must compile and/or run on a Linux ACS machine (e.g. ieng6.ucsd.edu), as this is where the verification of your solutions will occur. While you may develop your code on any system, ensure that your code runs as expected on an ACS machine prior to submission. You should test your code in the directories from which the zip files (see below) will be created, as this will approximate the environment used for grading the assignment.

Most of the points, except those for comments and style, will be awarded automatically, by evaluating your functions against a given test suite. The fourth file, test.py contains a very small suite of tests which gives you a flavor of these tests. At any stage, by typing at the UNIX shell :

python < test.py | grep "130>>" > log
you will get a report on how your code stacks up against the simple tests.

The last (or near the bottom) line of the file log must contain the word "Compiled" otherwise you get a zero for the whole assignment. If for some problem, you cannot get the code to compile, leave it as is with the raise ..., with your partial solution enclosed below as a comment. There will be no exceptions to this rule. The second last line of the log file will contain your overall score, and the other lines will give you a readout for each test. You are encouraged to try to understand the code in test.py, and subsequently devise your own tests and add them to test.py, but you will not be graded on this.

Alternately, inside the Python shell, type (user input is in red):

>>> import test
.
.
.
130>> Results: ...
130>> Compiled
and it should print a pair of integers, reflecting your score and the max possible score on the sample tests. If instead an error message appears, your code will receive a zero.



Submission Instructions

1. Create the zip file for submission

Your solutions to this assignment will be stored in separate files under a directory called pa3_solution/, inside which you will place the files: misc.py, spell.py, crack.py . These three files listed are the versions of the corresponding supplied files that you will have modified. There should be no other files in the directory.

After creating and populating the directory as described above, create a zip file called <LastName>_<FirstName>_pa6.zip by going into the directory pa3_solution and executing the UNIX shell command:

zip <LastName>_<FirstName>_pa6.zip *

2. Submit the zip file via the turnin program

Once you've created the zip file with your solutions, you will use the turnin program to submit this file for grading by going into the directory pa3_solution/ and executing the UNIX shell command:

turnin -c cs130w -p pa6 <LastName>_<FirstName>_pa6.zip

The turnin program will provide you with a confirmation of the submission process; make sure that the size of the file indicated by turnin matches the size of your zip file. See the ACS Web page on turnin for more information on the operation of the program.


Hints

May of the questions in this assignment use more advanced features of Python. You may find the following links useful.

Useful Links

Defining Classes

All classes used in this assignment should be new-style Python classes which have either object as a base class or only other new-style classes as base classes.
class foo(object):
    pass

class bar(foo):
    pass

Quirks / Features of Python


Problem #0: Documentation

None of the functions in this assignment are documented (except the Failure exception). You are expected to document all modules (.py files), all classes, and all public functions with doc strings. Doc strings should describe the behavior of the function/class/module. For example:
"The function prod takes two integers and returns their product".
If the implementation is not straightforward and obvious, there should be comments. For example:
# prod is implemented using a FFT to get O(n log n) time
Once documented you should get the following behavior at the Python prompt:

>>> import misc
>>> import decorators
>>> import streams
>>> help(misc)
Screen full of documentation with all your doc strings
>>> help(decorators)
Screen full of documentation with all your doc strings
>>> help(streams)
Screen full of documentation with all your doc strings

Problem #1: Warm-Up (misc.py)

(a) 10 points

Create a Python class Pair. Instances of Pair should have two public attributes: first and second. The constructor should take two optional arguments, first and second, which it should use to initialize the respective fields of the class. If either or both of these arguments are not present, the respective field or fields should be initialized with None. Finally, you should implement the __repr__ method to return a string of Python code that would reconstruct the instance. See here for more information on __repr__. This string should consist of the name of the class then an open parenthese followed by repr(first) and repr(second) separated by a comma followed by a space. After first and second, there should be a closing parentheses.

Once implemented you should get the following behavior at the Python prompt:

>>> from misc import *
>>> Pair(5,"foo")
Pair(5, 'foo')
>>> Pair()
Pair(None, None)
>>> Pair(4+5,"hi "*3)
Pair(9, 'hi hi hi ')
>>> Pair([1,2])
Pair([1, 2], None)
>>> Pair(second=0)
Pair(None, 0)
>>> p=Pair(5,6)
>>> [p.first,p.second]
[5, 6]
>>> class PairWrapper(Pair): pass
>>> PairWrapper(7,8)
PairWrapper(7, 8)

(b) 10 points

Write a function compose2(f,g) which takes a two functions, f, and g and returns a function which implements their composition. All of these functions do not have to be literal functions, but can be constructors or objects with a __call__ method. They can be anything that callable will return True on. Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from misc import *
>>> compose2(lambda x:x+"05",str)(4)
'405'
>>> compose2(list,set)([5,8,3,5,1,1,1,8,2,4,9])
[1, 2, 3, 4, 5, 8, 9]

(c) 15 points

Write a function compose which takes an arbitrary number of functions and returns a function which implements their composition. If compose is called with no arguments it should return the identity function with one argument. All of these functions do not have to be literal functions. They can be anything that callable will return True on. Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from misc import *
>>> compose(lambda x:x+"05",str)(4)
'405'
>>> compose(list,set)([5,8,3,5,1,1,1,8,2,4,9])
[1, 2, 3, 4, 5, 8, 9]
>>> compose()(4)
4
>>> from math import sqrt
>>> compose(str,int,sqrt,int,lambda x:x+"384",str,lambda x:x*x)(4)
'128'


Problem #2: Decorators (decorators.py)

In the file decorators.py there is an example decorator, profiled and stub code for decorators that you will be writing. At the bottom of the file are many examples of decorated functions. The expected output for these functions is available here: decorators.out.

(a) 30 points

Complete the definition for the decorator traced. When the decorated function is called, the decorator should print out an ASCII art tree of the recursive calls and their return values. The format of the tree should be as follows:
  1. Print a pipe symbol followed by a space ("| ") for every level of nested function calls.
  2. Print a comma then a minus sign then a space (",- ") next.
  3. Print the name of the function being traced followed by an open parenthesis followed by the repr() of all of the arguments. Arguments should be seperated by a comma followed by a space (", "). After the normal arguments, print all the keyword arguments in the form keyword then equals sign then repr() of the value of the keyword argument. The keyword arguments should also be seperated by a comma followed by a space. Keyword arguments should be printed in the order returnd by dict.items().
  4. Next increase the nesting level and call the function itself.
  5. At the original nesting level, print a pipe symbol followed by a space ("| ") for every level of nested function calls.
  6. Print a backquote then a minus sign then a space("`- ").
  7. Finally, print the repr() of the return value.
The return value of the function should be return to the caller after all printing is complete. If an exception occurs in the funciton, the nesting level must be adjusted to the appropriate level where the exception is caught. See change for an example.

Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from decorators import *
>>> @traced
>>> def foo(a,b):
...    if a==0: return b
...    return foo(b=a-1,a=b-1)
>>> foo(4,5)

,- foo(4, 5)
| ,- foo(a=4, b=3)
| | ,- foo(a=2, b=3)
| | | ,- foo(a=2, b=1)
| | | | ,- foo(a=0, b=1)
| | | | `- 1
| | | `- 1
| | `- 1
| `- 1
`- 1
1

(b) 30 points

Complete the definition of the memoized decorator. When the decorated function is called, the decorator should check to see if the function has already been called with the given arguments. If so, the decorator should return the value the the function returned when it was last called with the given arguments. If the function last threw an exception when called with the given arguments, the same exception should be thrown again. If the function has not been called with the given arguments, then call it and record the return value or exception. Then return the return value or raise the thrown exception.

Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from decorators import *
>>> from time import sleep
>>> @memoized
>>> def foo(a):
...    sleep(a)
...    return a
>>> foo(5)
# 5 second pause
5
>>> foo(5))
# practically instantaneous
5


Problem #3: Streams (streams.py)

(a) 5 points

Write a function words_in_line which will take a string and return a list of all the words in the line. Words are defined to be any sequence of non-whitespace characters.

Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from streams import *
>>> words_in_line(" Hello   World!")
['Hello', 'World!']

(b) 20 points

Write a function streamify which takes a function, f and returns a function which takes an object, l, which may be iterated over. The function, f may be assumed to take an item returned by an iterator over l and will return a list. The function returned by streamify should iterate over all item returned by the iterator over l and call f on each one. It should then iterate over the items returned by f and yeild() them.

Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from streams import *
>>> ws=streamify(words_in_line)
>>> for x in ws(iter(["Hello World!","Hello Python!","Goodbye Everybody"])):
...    print x
Hello
World!
Hello
Python!
Goodbye
Everybody

(c) 20 points

You can then define a function words_in_file which will return a stream of all the words in a file as follows:

words_in_file = compose(streamify(words_in_line),open)

Once you have done this at the prompt you should see something like:

>>> from streams import *
>>> for w in words_in_file("words"): print w
stroam
asarta
sinklike
...

Using this technique and the functions transform_digits, transform_capitalize and transform_reverse from PA3, to create a function, transformed_words_in_file, which will return an iterator over all possible transformations of all the words in a file. You must use compose and streamify. You must not use def. This function will the be used in crack_pass_file. Once you have implemented the function, you should get the following behavior at the Python prompt:

>>> from streams import *
>>> crack_pass_file("passwd","words","out")
Several minutes pass... Ctrl-C
Traceback (most recent call last):
...
KeyboardInterrupt

The file out should contain something like the following:

checani=asarta
root=...
...=...
...=...