T-Th 9:05 |
CS 1110: Introduction to Computing Using Python Fall 2012 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Main
About: Announcements Staff Consultants Times & Places Calendar Materials: Texts Python Command Shell Terminology Handouts: Lectures Assignments Labs Assessment: Grading Exams Resources: CMS Piazza (link) Piazza (about) AEWs FAQ Python API Style Guide Academic Integrity |
Assignment 1:
|
Code | Name | 1 USD = | Code | Name | 1 USD = | |
---|---|---|---|---|---|---|
AED | United Arab Emirates dirham | 3.67300014 | MAD | Moroccan dirham | 8.96073406 | |
ANG | Netherlands Antilles guilder | 1.74999869 | MDL | Moldovan leu | 12.4599723 | |
ARS | Argentine peso | 4.60600254 | MKD | Macedonian denar | 50 | |
AUD | Australian dollar | 0.952743902 | MUR | Mauritian rupee | 30.7002732 | |
BGN | Bulgarian lev | 1.59330049 | MXN | Mexican peso | 13.1385327 | |
BHD | Bahrain dinar | 0.377009934 | MYR | Malaysian ringgit | 3.12949865 | |
BND | Brunei dollar | 1.25080051 | NAD | Namibian dollar | 8.26268736 | |
BOB | Bolivian boliviano | 7.01001731 | NGN | Nigerian naira | 157.05984 | |
BRL | Brazil real | 2.02269873 | NIO | Nicaraguan cordoba | 23.689946 | |
BWP | Botswana pula | 7.73395205 | NOK | Norwegian krone | 5.96630232 | |
CAD | Canadian dollar | 0.988601426 | NPR | Nepalese rupee | 89.3415528 | |
CHF | Swiss franc | 0.978396994 | NZD | New Zealand dollar | 1.23823675 | |
CLP | Chilean peso | 482.392668 | OMR | Omani rial | 0.384949995 | |
CNY | Chinese yuan | 6.36589915 | PEN | Peruvian nuevo sol | 2.61500122 | |
COP | Colombian peso | 1821.49362 | PGK | Papua New Guinean kina | 2.12404418 | |
CRC | Costa Rican colon | 499.5005 | PHP | Philippine peso | 42.2904508 | |
CZK | Czech koruna | 20.2889141 | PKR | Pakistan rupee | 94.3930527 | |
DKK | Danish krone | 6.06369303 | PLN | Polish zloty | 3.3252972 | |
DOP | Dominican peso | 39.1251614 | PYG | Paraguayan guarani | 4424.77876 | |
DZD | Algerian dinar | 82.1962847 | QAR | Qatar riyal | 3.64090484 | |
EEK | Estonian kroon | 11.7303429 | RON | Romanian lei | 3.65570564 | |
EGP | Egyptian pound | 6.07651548 | RSD | Serbian dinar | 96.2927299 | |
EUR | Euro | 0.814663951 | RUB | Russian ruble | 31.8969092 | |
FJD | Fiji dollar | 1.78253119 | SAR | Saudi riyal | 3.75009375 | |
GBP | British pound | 0.636861546 | SCR | Seychelles rupee | 13.000013 | |
HKD | Hong Kong dollar | 7.75662804 | SEK | Swedish krona | 6.70627842 | |
HNL | Honduran lempir | 19.5000195 | SGD | Singapore dollar | 1.25080051 | |
HRK | Croatian kuna | 6.06818209 | SKK | Slovak koruna | 24.5428887 | |
HUF | Hungarian forint | 226.8088 | SLL | Sierra Leonean leone | 4329.00433 | |
IDR | Indonesian rupiah | 9523.80952 | SVC | Salvadoran colon | 8.74997813 | |
ILS | Israeli shekel | 4.04799301 | THB | Thai baht | 31.5497224 | |
INR | Indian rupee | 55.8784086 | TND | Tunisian dinar | 1.62299986 | |
JMD | Jamaican dollar | 89.1821992 | TRY | Turkish lira | 1.79870026 | |
JOD | Jordanian dinar | 0.708501307 | TTD | Trinidad dollar | 6.4 | |
JPY | Japanese yen | 79.1577614 | TWD | Taiwan dollar | 29.987705 | |
KES | Kenyan shilling | 84.0689365 | TZS | Tanzanian shilling | 1579.77883 | |
KRW | South Korean won | 1132.50283 | UAH | Ukrainian grivna | 8.0990022 | |
KWD | Kuwaiti dinar | 0.282719878 | UGX | Ugandan shilling | 2481.38958 | |
KYD | Cayman Islands dollar | 0.820001476 | USD | U.S. dollar | 1 | |
KZT | Kazakh tenge | 149.276011 | UYU | Uruguayan peso | 21.1501449 | |
LBP | Lebanese pound | 1506.0241 | UZS | Uzbekistani sum | 1912.04589 | |
LKR | Sri Lankan rupee | 132.013201 | VND | Vietnamese dong | 20833.3333 | |
LTL | Lithuanian litas | 2.8127971 | YER | Yemeni rial | 215.517241 | |
LVL | Latvian lats | 0.567099174 | ZAR | South African rand | 8.26268736 | |
MAD | Moroccan dirham | 8.96073406 | ZMK | Zambia kwacha | 4878.04878 |
Note however, that you should never use this table in any of the functions
that you write in a1.py
. The table above is for testing your
functions; not for writing them.
One of the most important outcomes of this assignment is that you understand the importance of testing. This assignment will follow an iterative development cycle. That means you will write a few functions, then fully test them before you write any more. This process makes it easier to find bugs; you know that any bugs must have been part of the work you did since the last test.
In this section we help you get started with this process. We also provide an overview of the rest of the assignment.
To do this assignment, Python must be set up properly. If you have not already done this, follow the installation instructions to set it up on your computer. Alternatively, you can just work in the ACCEL lab.
It is possible to do this assignment without the command shell, provided that you have added a "run button" to Komodo Edit. However, we suggest that you keep a command shell open as it can help with testing your program.
Finally, you will want to create a folder on your hard drive that is dedicated to this assignment and this assignment only. Every time that you work on a new assignment, we want you to make a new folder, as otherwise it is easy to mix up your assignments. Make sure that the command shell and Komodo Edit are both in the current folder before you start.
a1
In your newly created directory, you should create the module a1
(with file name a1.py
). This will be the main module for this assignment.
Following the
style guidelines, the first
three lines of this file should be single-line comments with (1) the module name, (2) your
name and netid, and (3) the date the file was last editted. Immediately after this, add
the following docstring:
"""Module for currency exchange
This module provides several String parsing functions to
implement a simple currency exchange routine using an
online currency service. The primary function in this
module is exchange()."""
This docstring is the module specification. We recommend that you cut-and-paste the docstring above exactly as it appears above. The purpose of this assignment is to expose you to specifications, not to have you write them on your own.
a1test
Iterative development hinges on proper unit testing, which was covered in
lab 3. In the same folder
as a1.py
, create the module a1test
(with file name a1test.py
). This will be the unit test for the
a1
module.
As with a1.py
, the first three lines of this file should be single-line
comments with (1) the module name, (2) your name and netid, and (3) the date the file
was last editted. Immediately after this, add the following Python code:
"""Unit test for module a1
When run as an application, this module invokes several
procedures that test the various functions in the module
a1."""
import cunittest
from a1 import *
As in lab 3, the module
cunittest
provides access to the functions assert_equals
and assert_true
. The second import statement allows unit test to access
all of the functions in a1
, and do it without putting the name in front
of each function.
Add four procedure stubs to this assignment: testA
, testB
,
testC
, testD
. Remember that a procedure stub should have the
keyword pass
(indented) after the header, but nothing else. We will add
our test cases to these procedures later.
Finally at the end of code>a1test, add the following code:
As with lab 3, this is the code that will execute when you "run it as an application". Recall that an application is a Python module that executes when you typeif __name__ == "__main__": testA() testB() testC() testD() print "Module a1 is working correctly"
from the command shell. You can also run an application by having its window as the active window in Komodo Edit and pressing the "run button".python module-name
The application code will call your four test procedures, which are (currently) empty. If everything is working, then the module print out the message
"Module a1 is working correctly"
If anything is wrong, then one of your assert statements in the test procedure will
stop the program before it reaches that line. Try this out now.
The rest of the assignment is broken into four parts (listed as Parts A, B, C, and D). In each part, you will do the following:
a1test
,
just before the final print statement.
Unless otherwise instructed, each test case should be a call to either
assert_true
or assert_equals
, as appropriate. Furthermore,
your tests should be representative. Refer back to the instructions for
lab 3 if these
directions are unfamiliar to you.
The descriptions that we provide in each part below represent the level of completeness and precision we are looking for in your docstring comments. In fact, it is best to copy-and-paste these descriptions to create the first draft of your docstring comments. If you do not cut and paste, please adhere to the conventions we use, such as using a single line, followed by a blank and a more descriptive paragraph, or by using "Returns: ..." for fruitful-functions. Using a consistent set of good conventions in this class will help us all.
If you want to see if your specifications are written correctly, start and interative Python shell and type
This should list all the functions with their specifications.>>> import a1 >>> help(a1)
As you can see from playing with the web service, a large part of this assignment is going involve breaking up the JSON string. One of the ways in which we need to break up the string is to separate the currency amount from the currency name. For exaample, if we are given the string
"0.814663951 Euros"Then we want to break it up into "0.814663951" and "Euros".
We are always guaranteed that there will be no spaces in the currency amount, and that the first space will be before the currency name (though there may be later spaces in the currency name itself, such as "U.S. Dollar"). Hence we just need the following two functions.
Returns: Substring of <s> up to, but not including, the first space
Precondition: <s> has at least one space in it
Returns: Substring of <s> after the first space
Precondition: <s> has at least one space in it
Implement these functions according to their specification. You should be able to do this in one or two lines using the find string method. Experiment with that method in the interactive shell if you do not completely understand the Python documentation.
Once you have implemented these two functions, it is time to test them. Add test cases
to the procedure testA
to test these two functions. The test cases should
be representative and test the functions thoroughly. Use the function assert_equals
in cunittest
to compare the result of these functions with the string that
you expect to get back. Our implementation has four test cases for each of the two functions
above.
To be complete, you should also add a specification to testA
to make
it clear which functions it is testing.
When you are done, try out the unit test. If something is not working properly, fix
it before proceding to the next step.
All of the responses to a currency query, whether valid or invalid, contain the keywords "lhs" and "rhs". If it is a valid currency query, then the answer is in quotes after the keyword "rhs". If it is invalid, then the quotes after rhs are empty. Hence the next step is to extract the information in quotes after these keywords.
The very first thing that you should do is add the following function to a1.py
Returns: The first substring of s between two (double) quote characters
A quote character is one that is inside a string, not one that delimits it. Often we use \" to distinguish this from ".
Example: If s is "A \"B C\" D"
, this function returns "B C"
Example: If s is "A \"B C\" D \"E F\" G"
, this function still returns
"B C"
because it only picks the first such substring.
Precondition: >s< is a string with at least two (double) quote characters inside.
You should have completed this function in lab 3. If you have not yet finished lab 3, finish it immediately. Because this function is technically part of the lab, and not the assignment, you make talk to students other than your partner about it (collaboration is always allowed on labs). This is the only function for which this is allowed. The rest of the functions are part of the assignment, so you may only collaborate with your partner.
Once you have this function completed, you should move on to the following functions:
Returns: The LHS value in the response to a currency query.
Given a JSON response to a currency query, this returns the string inside quotes (\") immediately following the keyword lhs. For example, if the JSON is
"{lhs: \"2 U.S. dollars\",rhs: \"1.629327902 Euros\",error: \"\",icc: true}"
then this function returns "2 U.S. dollars"
(not "\"2 U.S. dollars\""
). It returns
the empty string if the JSON is the result of on invalid query.
Precondition: <query> is the response to a currency query
Returns: The RHS value in the response to a currency query.
Given a JSON response to a currency query, this returns the string inside quotes (\") immediately following the keyword rhs. For example, if the JSON is
"{lhs: \"2 U.S. dollars\",rhs: \"1.629327902 Euros\",error: \"\",icc: true}"
then this function returns "1.629327902 Euros"
(not "\"1.629327902 Euros\""
). It returns
the empty string if the JSON is the result of on invalid query.
Precondition: <query> is the response to a currency query
Implement these two methods according to their specification. You should not need a conditional statement to implement these functions; simply find the position of the appropriate keyword and extract the value in quotes immediately after it. Your implementation will make heavy use of the find string method.
As you can see from the specification, first_inside_quotes(s)
is a useful
helper function for each of these two functions. With this helper function, each of these
functions should only be a few lines.
Add test cases to procedure testB
to test these functions. If you have completed
lab 3, you
should already have test cases for first_inside_quotes(s)
. Add those to
the test procedure.
To test the others, you will need to try out several JSON responses. To get some JSON responses for testing, enter a query URL into the web service and copy the result into a test case. Once you have enough JSON responses, run the unit test and make sure everything is working before going to the next step.
Now it is time to interact with the web service. In this part, you will implement a single function.
Returns: A JSON string that is a response to a currency query.
A currency query converts <amount_from> money in currency <currency_from> to the currency <currency_to>. The response should be a string of the form
"{lhs: \"<old-amount>\",rhs: \"<new-amount>\",error: \"\",icc: true}"
where the values <old-amount> and <new-amount> contain the value and
name for the original and new currencies. If the query is invalid, both
<old-amount> and <new-amount> will be empty.
Precondition: <amount_from> is of type float, while <currency_from> and <currency_to> are of type string
While this function sounds complicated, it is actually the simplest function so far and
can be implemented in two lines. You need to use the
urlopen
function from the module urllib2
. As shown in class, this function takes
a string that represents a URL and returns an object of type "instance" that represents
the web page for that url. This object has the following methods:
Method | Specification |
---|---|
geturl() | Returns: The URL address of this web page as a string. |
read() | Returns: The contents of this web page as a string. |
These methods are enough to implement the function above.
Once again, test cases to testC
to test this function.
You need to ensure that the function returns exactly the right JSON
string for the value given. The best way to test this is to use a web
browser to manually get the right JSON answer and copy it into
testC
; then check that the function returns the same
JSON string. Remember to be thorough with your choice of test cases;
one is not enough.
You only need to test your function on different currency queries.
You do not need to test it on other web pages, or web pages that
do not exit. In fact, if you use urlopen()
on an
invalid URL, it will return None
. As we saw in class,
calling a method on None
causes an error.
Important: Connecting to, and reading from, a web page is not instantaneous. It will take several seconds for this function to complete. Furthermore, if you wait until the last minute to test this assignment, you will be connecting to the same web page as everyone else in the class, so it might run even slower. You should give each call to this function at least 10-20 seconds to complete before restarting the test.
There is another issue if you are running the unit test with the "run button" and not from the command shell. It appears that Komodo delays all of the print statements until the unit test is complete. So you will see nothing for a while (almost minute, if you have a lot of tests) and then you will see everything at once. We recommend running this unit test from the command shell, so that you can see each print statement the second it is executed.
We are now ready for the final part of the assignment. Implement the following functions according to their specification.
Returns: True if <currency> is a valid (3 letter code for a) currency.
Precondition: <currency> is a string.
Returns: amount of currency received in the given exchange.
In this exchange, the user is changing <amount_from> money in currency <currency_from> to the currency <currency_to>. The value returned is a float representing the amount in currency <currencyTo>
Precondition: <amount_from> is a float. Both <currency_from> and <currency_to> are strings with valid three-letter currency codes.
In implementing iscurrency(currency)
, you should not
use the table of currencies. That would make a
very large function with a lot of if-statements. You are not allowed if-statements
in this lab. Instead, you should figure out how to use currency_response
as a helper method to implement iscurrency
.
One last time, add test cases to procedure testD
for these two
functions. In the case of iscurrency
, you will find the
exchange table useful in determining correct
answers for your test cases. While it is not okay to use the table in
iscurrency
itself, it is okay to use the table to test the
function.
You may also use the table to test the function exchange
.
However, you might find it easier to use a currency query URL to look up the correct
answer, and then paste the answer into your test case.
A bigger issue with testing exchange
is that problem that we
saw the second week of class: floats are not exact. Floats in Python have
roughly 15 significant digits. If you use ==
to compare to
floats, you might get a value of False, even though they are the same,
because they differ on the insignificant digits.
To solve this problem,
cunittest
provides a function called assert_floats_equal
.
This function is exactly like assert_equals
except that it is
custom designed for floats; it disregards the insignificant digits and only
compares the significant ones. You should use this function to test
exchange
instead of assert_equals
.
Finally, bear in mind that, like currency_response
, these functions
connect to the web service, and so are not instantaneous. In our solution,
with complete test procedures for everything, it can take up to 10 seconds to
run the unit test on campus. This will be a bit slower if you are working
closer to the deadline. Again, we recommend that you run the unit test from
the command shell, and not the "run button" in Komodo Edit so that you can
see print statements as they are executed.
Once you have everything working you should go back and make sure that your program meets the class coding conventions. In particular, you should check that the following are all true:
Upload the files a1.py
and a1test.py
to CMS by
the due date: Monday, September 17th at 11:59 pm.
Do not submit any files with the extension/suffix .pyc
. It will
help to set the preferences in your operating system so that extensions always appear.
Check the CMS daily until you get feedback from a grader. Make sure your CMS notifications for CS 1110 are set so that you are sent an email when one of your grades is changed.
Within 24 hours, do RRRRR: Read the feedback, Revise your program accordingly, Resubmit, and Request a Regrade using the CMS. If you do not request a regrade, we have no simple way of knowing that you have resubmitted.
This whole process, starting from first submission on Monday, September 17th, continues until you submit a solution that demonstrates complete mastery; in some cases this may require multiple additional resubmits by you. You need to complete this process within one week.
In addition to turning in the assignment, we ask that you complete the new survey posted in CMS. As this is the first time that we are teaching the course in Python, we will have surveys after each of the assignments. These surveys will ask about things such as how long you spent on the assignment, your impression of the difficulty, and what could be done to improve it.
Please try to complete the survey within a day of turning in this assignment. Remember that participation in surveys compromise 1% of your final grade. We also ask that you be honest in your answers.
This section is not part of the assignment. It is optional. Furthermore, do not make the changes in this section to the file that you submit for grading. It will be sent back to you to fix.
So far you have worked with a simulated currency exchange service. But with a few changes you can use the real thing. In the web service instructions we told you to use the URL prefix
http://www.cs.cornell.edu/courses/cs1110/2012fa/calculator.php?
If you change that prefix to
http://www.google.com/ig/calculator?
you will use Google's calculator instead. Try that out on converting dollars to Euros
(pick small values for now).
Run the exchange function four or five times. See the value change? That is one of the reasons we did not use Google; that is too hard to test against. In fact, even employees at Google would do what we did: write a program against an unchanging exchange service before deploying it against the real thing.
There is another reason why we are not using Google. Run currencyResponse
to get the JSON string for 1000 U.S. dollars to IDR (Indonesian rupiahs). Though the
numbers may be different, the rhs value of JSON string will look something roughly like this:
rhs: "9.52380952 million Indonesian rupiahs"
Once a value gets above a million, Google "anglicizes" the number, spelling out the
word "million". There is a space before "million", and so your function will give
the exchange rate as 9.52380952. This is incorrect, but it is not your fault; Google
violated the precondition of your functions.
Solving this second problem is beyond the scope of this assignment. We will revisit it later in the course when we have the right tools. Right now, the best that you can do is implement a new version of exchange with the following specification:
def google_exchange(amount_from, currency_from, currency_to):
"""Returns: description of currency received in exchange.
This function connects to the Google currency converter to change
<amount_from> money in currency <currency_from> to
the currency <currency_to>. The value returned is string with
the amount and name of the new currency.
Example: "9.52380952 million Indonesian rupiahs"
Precondition: <amount_from> is a float. Both <currency_from>
and <currency_to> are strings with valid three-letter currency
codes."""
Feel free to implement this function if you wish. We will not grade it, and you will not get credit for it.