T-Th 9:05 or 11:15 in Kimball B11 |
CS 1110: Introduction to Computing Using Python Spring 2013 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Main
About: Announcements Staff Consultants Times & Places Syllabus Materials: Texts Python/Komodo Command Shell Terminology Handouts: Lectures Assignments Labs Assessment: Grading Exams Resources: CMS Piazza (link) Piazza (about) VideoNote AEWs FAQ Python Library Online Python Tutor Style Guide Academic Integrity Authors of these pages: D. Gries, L. Lee, S. Marschner, & W. White, over the years |
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 not use the values in 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, and there is no reason for you to waste your
time hard-coding in each of the approximately zillion currencies listed in this
table into your program, since the web service you'll contact already knows them all anyway.
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 and the Cornell Extensions must be set up properly on the computer you plan to work on. 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 run your unit test on the command line (type python a1test.py
)
when you get near the end of the assignment,
for reasons given below.
Finally, 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, to keep things organized and avoid problems with naming collisions. Make sure that the command shell and Komodo Edit are both open in the current folder before you start.
Put the file cunittest.py
in this directory.
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 edited. 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 directly cut-and-paste this docstring into a1
.
For now, we want to expose you to specifications, not have you
write them on your own.
a1test
Iterative development hinges on proper unit testing, which was covered in
lecture
and
lab. 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 the authoring-info 3-comment header.
Immediately after it, 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
import a1
Add four procedure stubs to this a1test
: testA()
, testB()
,
testC()
, testD()
, separated from each other by two blank lines. Remember that a procedure stub has a function header and then
the keyword pass
(indented) after the header, but nothing else. You'll add
test cases to these procedures later.
Finally, at the end of a1test.py
, add the following application code (see
lecture slides for more on application code).
if __name__ == "__main__": testA() testB() testC() testD() print "Module a1 passed all test cases"
The application code will call your four test procedures, which are (currently) empty. If everything is working, then the module, when run, prints out the message
"Module a1 passed all test cases"
Try it.
The rest of the assignment is broken into Parts A, B, C, and D. In each part, do the following:
a1.
Then, admire the completeness of the specifications
we give you for each function, and then copy each one (copy-and-paste is fine) into the corresponding function body, indented.
a1test
for each function —
yes, before writing the function bodies, as we talked about in
lecture.
a1test
. If errors are found, fix them and re-test; rinse and repeat until no more errors are found.
Unless otherwise instructed, each test case should be a call one of the
cunittest
assertion procedures.
Your tests should be representative, meaning that they should cover the space of possible inputs; we exercised this concept in
lab.
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 copy-and-paste, please adhere to the conventions we use, such as using a single line, followed by a blank and a more descriptive paragraph, using "Returns: ..." for fruitful functions, and so on. Using a consistent set of good conventions in this class will help us all.
One way to check that your specifications are written correctly is to start an interactive Python shell and type
This should list all the functions with their specifications.>>> import a1 >>> help(a1)
Back to extracting currency information from a JSON string.
The first step we want you to take is Conceptually,
our first goal is to be able
to separate the currency amount from the currency name.
For example, if we are given the string
"0.814663951 Euros"then we want to break it up into "0.814663951" and "Euros".
We guarantee 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. To accomplish this task, you are to write two general
helper functions, which can apply to more than just strings
representing currency amounts, but to any strings containing at least one space.
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 specifications in the way described in the section
“Instructions for the Remainder of the Assignment” above: write header and specification
in a1.py
; give testA
in a1test.py
a specification about what functions
it's testing and write test cases for your functions in testA
; fill in the function
body in a1
; test by running a1test
; correct any errors and re-test until no
errors remain. Your function bodies will probably have only one or two lines and use
find()
or index().
Your test cases should make use of assert_equals
in cunittest.py
.
Our implementation has four test cases for each of the two functions
above. When you think about what test cases you want to include, consider:
does the specification allow for strings with more than one space? Strings that start with
a space? Strings that don't have any spaces?
All responses to a currency query to our web service, whether valid or invalid, contain the keywords "money_in" and "money_out". In the case of a valid currency query, the answer is in quotes after the keyword "money_out". If it is invalid, then the quotes after money_out are empty. Hence the next step is to extract the information in quotes after these keywords.
First, add the following function to a1.py.
Again, do this by following the steps outlined
in “Instructions for the Remainder of the Assignment”:
put test cases in a1test.testB()
before writing function bodies in a1
.
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. We can use ' to delimit the string if we want to use " inside it.
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.
Next, add the following functions. To create test cases, 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.
Returns: The money_in 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 money_in. For example, if the JSON string is
'{money_in: "2 U.S. dollars",money_out: "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 string is the result of an invalid query.
Precondition: <query> is the response to a currency query
Returns: The money_out 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 money_out. For example, if the JSON string is
'{money_in: "2 U.S. dollars",money_out: "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 string is the result of an invalid query.
Precondition: <query> is the response to a currency query
Returns: The error 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 error. For example, if the JSON string is
'{money_in: "",money_out: "",error: "4",icc: false}'
then this function returns "4"
(not '"4"'
or the number 4
). The returned string will
be non-empty if a currency-conversion error occurred, for example, if invalid currency keywords were supplied in the query.
It returns the empty string if the JSON string is the result of a valid query.
Precondition: <query> is the response to a currency query
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 implementations must use the
find or index() string methods, plus the helper function
first_inside_quotes()
you already wrote (or a function that uses first_inside_quotes
).
Now it is time to interact with the web service. In this part, you will implement a single
function. The test cases should go in procedure testC()
in a1test.py
; don't
forget to specify testC()
properly.
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
'{money_in: "<old-amount>",money_out: "<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.
Preconditions: <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
that we saw in
lab 2.
Recall that this function takes
a string that represents a URL and returns an object of type “instance” that represents
the web page for that url. Such an 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. |
Using one or both of these methods is enough to implement the function above.
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 enter
a web-service URL, such as
http://cs1110.cs.cornell.edu/2013sp/a1/calculator.php?q=2.5USD=?EUR , in your browser to manually get the right JSON answer and copy it into a test case in
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.
Important:
Fetching a web page takes time, especially if too many people are trying to do so at the simultaneously. You should give each call to this function at least 10-20 seconds to complete before restarting any tests.
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 a minute, if you have a lot of tests)
and then you will see everything at once. This can get confusing if there
are multiple outputs, for instance, if you have put in many print statements to debug your code.
We recommend running this unit
test from the command shell, so that you can see each print statement
the second it is executed: type python a1test.py
at the command prompt (not the Python command prompt).
We are now ready for the final part of the assignment. Implement the following
functions according to their specification, again using our test-case-before-function-body methodology.
The test cases should go in procedure testD
in a1test
,
which you should properly specify. You may wish to use assert_true
as opposed to assert_equals
in some of your test cases.
Returns: True if <currency> is a valid 3-letter code for a currency, False otherwise
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>. Preconditions: <amount_from> is a float. Both <currency_from> and <currency_to> are strings that are 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 must use currency_response
and get_error()
as helper methods to implement iscurrency
.
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 decide on some test cases.
You may also use the table to craft some test cases for the function exchange
.
However, you will probably 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 in class: real numbers cannot be represented exactly, which can lead to problems when trying to test equality. To solve this problem,
cunittest
provides a function called assert_floats_equal
,
which you encountered in lecture and lab.
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 via not the "run button" in Komodo Edit, so that you can
see print statements as they are executed.
Once you have everything working, go back and make sure that your program meets the class coding conventions, including the following:
Upload the files a1.py
and a1test.py
to CMS by
the due date: Monday, February 18th 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. To find the feedback, click on the Assignment 1 link in CMS; on the page you're brought to, click on the red word “show” in the line “Grading Comments & Requests (show)&rqduo; You can contact your grader if you have questions about their feedback; you can see their netid in the place you can see their feedback.
Within 24 hours of getting feedback, 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, February 18th, continues until you submit a solution that demonstrates complete mastery; in some cases this may require multiple additional resubmits by you. You need to have submitted a final, correct version by Monday, February 25th, which means you'll probably want to have re-submitted at least once before then.
This section is not part of the assignment. It is optional. Furthermore, do not make the changes suggested in this section to the files that you submit for grading; if you do, they 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://cs1110.cs.cornell.edu/2013sp/a1/calculator.php?
If you change that prefix to
http://www.google.com/ig/calculator?
you will use Google's calculator instead. And, our server has a slightly different output
format: Google uses “lhs” and “rhs” instead of money_in and money_out.
You should be able to modify your code to handle the Google calculator's output format.
Do so, and you can try it 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.