T-Th 9:05
or
T-Th 11:15
in Olin 155

CS 1110: Introduction to Computing Using Python

Fall 2014

Assignment 1:
Currency

Due to CMS by Friday, September 19th at 11:59 pm.

Important (9/17/14): See Part B for an important update to the specification for get_value

Thinking about that trip overseas? If you can swing it, it is best to go when the exchange rate is in your favor. When your dollars buy more in the foreign currency, you can do more on your vacation. This is why it would be nice to have a function that, given your current amount of cash in US dollars, tells you how much your money is worth in another currency.

However, there is no set mathematical formula to compute this conversion. The value of one currency with respect to another is constantly changing. In fact, in the time that it takes you to read this paragraph, the exchange rate between the dollar and the Euro has probably changed several times. How on Earth do we write a program to handle something like that?

One solution is to make use of a web service. A web service is a program that, when you send it web requests, automatically generates a web page with the information that you asked for. In our case, the web service will tell us the current exchange rate for most of the major international currencies. Your job will be to use string-manipulation methods to read the web page and extract the exact information we need. Full instructions are included below.

Learning Objectives

This assignment is designed to give you practice with the following skills:

  • How to write a self-contained module in Python
  • How to use string and object methods in Python
  • How to connect Python to a web service
  • How to read specifications and understand preconditions
  • How to use docstrings appropriately for specifications
  • How to follow the coding conventions for this course
  • How to thoroughly test a program

The functions we ask you to write in this assignment are relatively short and straightforward. The emphasis is testing and "good practices", not complicated computations. You will find the most recent lab very helpful in understanding this assignment.


Table of Contents

Authors: W. White, D. Yoon, Q. Jia, L. Lee, and S. Marschner. Currency calculator code by N. Gavalas.

Image Credit: Petr Kratochvil


Academic Integrity and Collaboration

This assignment is a slightly modified version of an assignment given in previous semesters. Please do this assignment without consulting (or seeking) previous solutions. Since you are allowed to revise and resubmit, with help from us, until you have mastered this assignment, there is no reason to not do this assignment on your own. Furthermore, consulting any prior solution is a violation of CS1110's academic integrity policies.

We also ask that you do not enable violations of academic policy. Do not post your code to Pastebin, GitHub, or any other publicly accessible site. This is also a violation of academic integrity.

Collaboration Policy

You may do this assignment with one other person. If you are going to work together, form your group on CMS as soon as possible. This must be completed before you submit the assignment. Both people must do something to form the group. The first person proposes, and then the other accepts. You have to do this early because CMS does not allow you to form groups once grades are released. Once you've grouped on CMS, only one person submits the files.

If you do this assignment with another person, you must work together. It is against the rules for one person to do some programming on this assignment without the other person sitting nearby and helping. Take turns "driving"; alternate using the keyboard and mouse.

With the exception of your CMS-registered partner, you may not look at anyone else's code or show your code to anyone else (except a CS1110 staff member) in any form whatsoever.


Before You Get Started

Read Carefully

These instructions may seem long, but that is because we have tried to give you all the information you need in one document. Your chances of completing the assignment quickly will be increased by reading carefully and following all instructions. Many requests for resubmission are caused not by issues with programming but simply by not following instructions.

Pay particular attention to the section on Iterative Development, as it contains important instructions for the remaining sections, and we will not repeat these instructions for each section.

Start Early!

600 students trying to contact a web service at once will slow everybody down. Connecting to, and reading from, a web page is not instantaneous. It will take several seconds for some of the functions you will write 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 things could slow down even more.

Grading Policy (Revise-and-Resubmit Cycle)

To ensure that everyone masters this assignment, we will use an iterative feedback process. If one of the objectives below is not properly met, we will give you feedback and expect you to revise and resubmit. This process will continue until you are done. This process should be finished by Friday, September 26th; Once you finish you will receive a perfect score of 10. In our experience, almost everyone is able to achieve a perfect score within two submissions.

In grading your code, we will focus on the following issues in order:

  • Correct function specifications and/or formatting
  • Adequate test cases
  • Correctness of the code (does it pass our test cases?)

Formatting is graded according to the course style guidelines, available on the course web page.

If your code fails one of the three tests above, we will notify you and ask you to submit. We stop checking once we find the first few errors, so you should not assume that the errors we point out are the only errors present.

Until we have decided that you have mastered (e.g. 10/10) the assignment, your "grade" on CMS will be the number of revisions so far. This allows us to keep track of your progress. Do not be alarmed if you see a "1" for the assignment at first! The assignment will be considered completed when it passes all three steps outlined above.

Assignment Scope

Everything that you need to complete this assignment should have been covered by Lecture 6 (Objects) in class. In particular, you may not use if-statements anywhere in this assignment, as they are not necessary. Submissions containing if-statements will be returned for you to revise. Similarly, students with prior programming experience should not try to use loops or recursion.

Getting Help

If you do not know where to start, if you do not understand testing, or if you are completely lost, please see someone immediately. This can be the course instructor, a TA, or a consultant. Do not wait until the last minute, particularly since this is due just after a weekend. A little in-person help can do wonders. See the staff page for more information.


The Currency Exchange Web Service

Before you do anything at all, you might want to play around with the currency exchange web service. You do not need any Python to do this; just a web browser.

For this assignment, you will use a simulated currency exchange service that never changes values. This is important for testing; if the answer is always changing, it is hard to test that you are getting the right answers. The appendix explains how you can make a few minor changes to hook your program up to Rate-Exchange currency calculator, to get real-time currency-exchange results. However, we do not want you to submit such code for your assignment; stick with the course web server.

To use the service, you employ special URLs that start with the following prefix:

  http://cs1110.cs.cornell.edu/2014fa/a1/calculator.php?

This prefix is followed by a currency query. A currency query has three pieces of information in the following format (without spaces; we have included spaces here solely for readability):

  from=source & to=target & q=amount

where source is a three-letter code for the original currency, target is a three-letter code for the new currency and amount is a float value for the amount of money in the original. For example, if you want to know the value of 2.5 dollars (USD) in Euros (EUR), the query is

  from=USD&to=EUR&q=2.5

The full URL for this query is

http://cs1110.cs.cornell.edu/2014fa/a1/calculator.php?from=USD&to=EUR&q=2.5

Click on the link to see it in action.

You will note that the "web page" in your browser is just a single line in the following format:

  {"to": "EUR", "rate": 0.77203, "from": "USD", "v": 1.93007500000000}

This is what is known as a JSON representation of the answer. JSON is a way of encoding complex data so that it can be sent over the Internet. You will use what you know about string operations and methods to pull out the relevant data out of the JSON string.

You should try a few more currency queries to familiarize yourself with the service. Note that if you enter an invalid query (for example, using a non-existent currency code like "AAA"), you will get the following response in error:

  {"err": "failed to parse response from xe.com."}

Let's try something more extreme this time. If you enter a query without any parameters passed, you will get this:

  {"err": "invalid request"}

This will be important for error handling in this assignment.

Your query can be partially valid, but still have unorthodox parts. In this case, the server responds with a warning. For example, if you supply a query with two valid currency codes, but with an invalid quantity value, you will get the following warning:

  {"to": "EUR", "rate": 0.77203, "warning": "invalid quantity, ignored.", "from": "USD"}

But for this assignment, you can ignore these warning cases (this will be part of the function precondition). We limit the assignment to completely erroneous queries or perfectly well-formed queries only only.


Focus of the Assignment

Your primary goal in this assignment is to use the currency exchange service to write the following function:

def exchange(currency_from, currency_to, amount_from):
    """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 represents the amount in currency currency_to.

    The value returned has type float.

    Precondition: currency_from and currency_to are of type string,
    and are both valid three-letter currency codes.  The value
    amount_from is a float"""

This function will involve several steps. You will get the JSON string from the web service, break up the string to pull out the numeric value (as a substring), and then convert that substring to a float. As this is the very first assignment, we are going to take you through this process step-by-step.

This assignment might feel like you are working in reverse. You will write the functions to break up the string first, and the functions to interact with the web service last. This is because we want you to develop the following programming habit: always complete and test the helper functions before finishing the functions that use them.


Currency Exchange Table

In order to make it easier to test your program, we have fixed the exchange rates in our web service. That way you can test the answer in a web browser (using a currency query URL) and then compare the results to your Python program, without worrying about rates fluctuating.

The following currencies are supported by our web service:

Code Name 1 USD = Code Name 1 USD =
AED United Arab Emirates dirham 3.672930 LKR Sri Lankan rupee 130.2050
AFN Afghan afghani 55.59990 LRD Liberian dollar 92.49990
ALL Albanian lek 107.8000 LSL Lesotho loti 10.69920
AMD Armenian dram 409.6600 LTL Lithuanian litas 2.665680
ANG Netherlands Antillean guilder 1.790000 LYD Libyan dinar 1.226980
AOA Angolan kwanza 97.51100 MAD Moroccan dirham 8.540400
ARS Argentine peso 8.415000 MDL Moldovan leu 14.12480
AUD Australian dollar 1.066270 MGA Malagasy ariary 2544.980
AWG Aruban florin 1.790000 MKD Macedonian denar 47.43480
AZN Azerbaijani manat 0.784300 MMK Myanmar kyat 973.9940
BAM Bosnia and Herzegovina convertible mark 1.509960 MNT Mongolian tugrik 1824.480
BBD Barbados dollar 2.000000 MOP Macanese pataca 7.982600
BDT Bangladeshi taka 77.47670 MRO Mauritanian ouguiya 290.4980
BGN Bulgarian lev 1.510120 MUR Mauritian rupee 31.64990
BHD Bahraini dinar 0.377099 MVR Maldivian rufiyaa 15.38000
BIF Burundian franc 1534.990 MWK Malawian kwacha 394.4310
BMD Bermudian dollar 1.000000 MXN Mexican peso 13.03400
BND Brunei dollar 1.253990 MYR Malaysian ringgit 3.182500
BOB Boliviano 6.909640 MZN Mozambican metical 30.39990
BRL Brazilian real 2.240400 NAD Namibian dollar 10.69920
BSD Bahamian dollar 1.000000 NGN Nigerian naira 162.3500
BTN Bhutanese ngultrum 60.20000 NIO Nicaraguan córdoba 26.18500
BWP Botswana pula 8.928600 NOK Norwegian krone 6.283200
BYR Belarusian ruble 10499.79 NPR Nepalese rupee 97.86750
BZD Belize dollar 2.032360 NZD New Zealand dollar 1.200700
CAD Canadian dollar 1.088150 OMR Omani rial 0.385100
CDF Congolese franc 921.8460 PAB Panamanian balboa 1.000000
CHF Swiss franc 0.931450 PEN Peruvian nuevo sol 2.856990
CLP Chilean peso 586.3400 PGK Papua New Guinean kina 2.475250
CNY Chinese yuan 6.140700 PHP Philippine peso 43.65000
COP Colombian peso 1935.000 PKR Pakistani rupee 102.0770
CRC Costa Rican colon 541.9000 PLN Polish zloty 3.225600
CUC Cuban convertible peso 1.000000 PYG Paraguayan guaraní 4282.110
CUP Cuban peso 26.50000 QAR Qatari riyal 3.641500
CVE Cape Verde escudo 84.79700 RON Romanian new leu 3.398600
CZK Czech koruna 21.31940 RSD Serbian dinar 91.93550
DJF Djiboutian franc 181.1980 RUB Russian ruble 36.88680
DKK Danish krone 5.748800 RWF Rwandan franc 687.9760
DOP Dominican peso 43.46270 SAR Saudi riyal 3.750350
DZD Algerian dinar 80.53000 SBD Solomon Islands dollar 7.348780
EGP Egyptian pound 7.152500 SCR Seychelles rupee 13.00250
ERN Eritrean nakfa 10.47000 SDG Sudanese pound 5.692500
ETB Ethiopian birr 19.89750 SEK Swedish krona 7.099000
EUR Euro 0.772030 SGD Singapore dollar 1.253990
FJD Fiji dollar 1.866370 SHP Saint Helena pound 0.612407
FKP Falkland Islands pound 0.612407 SLL Sierra Leonean leone 4385.000
GBP Pound sterling 0.612407 SOS Somali shilling 1198.980
GEL Georgian lari 1.742020 SRD Surinamese dollar 3.250000
GHS Ghanaian cedi 3.732490 STD Sãmo Tomé and Príncipe dobra 18950.16
GIP Gibraltar pound 0.612407 SYP Syrian pound 154.1000
GMD Gambian dalasi 39.59370 SZL Swazi lilangeni 10.69920
GNF Guinean franc 7020.990 THB Thai baht 31.98500
GTQ Guatemalan quetzal 7.724500 TJS Tajikistani somoni 4.971690
GYD Guyanese dollar 205.6990 TMT Turkmenistani manat 2.850000
HKD Hong Kong dollar 7.750100 TND Tunisian dinar 1.760500
HNL Honduran lempira 21.12590 TOP Tongan pa'anga 1.850140
HRK Croatian kuna 5.888800 TRY Turkish lira 2.159000
HTG Haitian gourde 45.45240 TTD Trinidad and Tobago dollar 6.328290
HUF Hungarian forint 242.5430 TWD New Taiwan dollar 29.91000
IDR Indonesian rupiah 11744.20 TZS Tanzanian shilling 1660.470
ILS Israeli new shekel 3.602290 UAH Ukrainian hryvnia 12.82700
INR Indian rupee 60.20800 UGX Ugandan shilling 2606.950
IQD Iraqi dinar 1162.990 USD United States dollar 1.000000
IRR Iranian rial 26638.25 UYU Uruguayan peso 24.10990
ISK Icelandic króna 117.1500 UZS Uzbekistan som 2351.830
JMD Jamaican dollar 112.4980 VEF Venezuelan bolívar 6.292500
JOD Jordanian dinar 0.708750 VND Vietnamese dong 21172.98
JPY Japanese yen 105.0850 VUV Vanuatu vatu 94.57730
KES Kenyan shilling 88.65000 WST Samoan tala 2.303090
KGS Kyrgyzstani som 53.54950 XAF CFA franc BEAC 506.4180
KHR Cambodian riel 4064.880 XAG Silver (one troy ounce) 0.052043
KMF Comoro franc 379.8140 XAU Gold (one troy ounce) 0.000788
KPW North Korean won 132.4220 XBT BitCoin 0.002080
KRW South Korean won 1024.700 XCD East Caribbean dollar 2.700000
KWD Kuwaiti dinar 0.285640 XPD Palladium (one troy ounce) 0.001124
KYD Cayman Islands dollar 0.835187 XPT Platinum (one troy ounce) 0.000709
KZT Kazakhstani tenge 182.0150 YER Yemeni rial 214.9050
LAK Lao kip 8037.290 ZAR South African rand 10.69920
LBP Lebanese pound 1512.490 ZMW Zambian kwacha 6.050000
LKR Sri Lankan rupee 130.2050 ZWD Zimbabwe dollar 361.9000

Note however, that you should not 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. There is no reason for you to waste your time hard-coding in all of the currencies listed in this table into your program, since the web service you will contact already knows them all anyway.


Iterative Development (How to Work Through the Assignment)

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.


Setting up Python

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.

You should also 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.


The Module 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) the name and netid of the authors, 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 into a1. For now, we want to expose you to specifications, not have you write them on your own.

The Module 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 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 a script, this module invokes several procedures that 
test the various functions in the module a1."""

import cornelltest
import a1

If you don't have the cornelltest module, you can get it here. As in lab 3, the module cornelltest provides access to the functions assert_equals and assert_true. The second import statement allows the unit test to access all of the functions in a1.

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 a1test, add the following script code:

if __name__ == '__main__':
    testA()
    testB()
    testC()
    testD()
    print "Module a1 passed all tests"
See the lecture slides on how application code works.

The script code will call your four test procedures, which are (currently) empty. If everything is working, then the module print out the message

"Module a1 passed all tests"

Try this out.


Instructions for the Remainder of the Assignment

The rest of the assignment is broken into four parts (listed as Parts A, B, C, and D). In each part, do the following:

Write a function header into a1

We will give you the header to write. We will also give you a detailed docstring specification for the function. You should copy-and-paste the specification into the function body, indented.

Add test cases to a1test

Yes, this means you are writing tests before writing the function bodies. We talked about this in lecture.

Unless otherwise instructed, each test case should be a call to an assert function in cornelltest. Furthermore, your tests should be representative. Refer back to the instructions for lab 3 if you do not understand what we mean by this.

Write the function bodies

Make sure that the function satisify the specifications exactly. If the specification says to return something, you need a return statement. Make sure that the value returned is of the correct type.

Run the unit test a1test

If errors are found, fix them and re-test. Keep doing this until no more errors are found.


Writing Function Specifications

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 line 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 an interactive Python shell and type

>>> import a1
>>> help(a1)
This should list all the functions with their specifications.


Part A: Breaking Up Strings

A large part of this assignment is breaking a JSON into its component parts. A JSON string is a sequence of key-value pairs separated by commas. The key is a description of what the value means (like from or rate), since a JSON typically stores more than one value. For example, the substring

  '"rate": 0.75443'

is a key-value pair with key 'rate' and value 0.75443.

The most important thing to do with a JSON is to extract out the substrings for the values. For example, consider the following JSON:

  '{"to": "EUR", "rate": 0.77203, "from": "USD", "v": 1.93007500000000}'

Each value immediately follows a space. The end of the value is indicated by a comma or, if it is the value for 'v', by the curly brace at the end. This is the motivation for the two functions below.



def after_space(s):
    """Returns: The substring of s after the first space.
    
    Precondition: s is a string with at least one space in it."""

def before_comma_or_end(s):
    """Returns: Substring of s up to but not including, the first comma.
    
    If the string does not contain a comma, then the function returns
    all but the last element in the string.  THIS FUNCTION SHOULD NOT 
    HAVE ANY IF STATEMENTS.
    
    Example: If s is 'a,b,c', this function returns 'a'.
    Example: If s is 'abc}', it returns 'abc' (there is no comma).
    
    Precondition: s is a nonempty string"""

You should implement these functions according to their specification, specification, as described in the Instructions for the Remainder of the Assignment. In other words,

  • Write the header and specification in a1.py
  • Place test cases in the procedure testA() of a1test.py
  • Implement the functions in a1.py.
  • Test for and correct errors until no errors remain.

The implementation of after_space(s) is relatively simple, and should be only a few lines. The implementation of before_comma_or_end(s) seems to be a little more complicated, particularly because you have to deal with the case in which there are no commas in the string. Those of you with programming experience might be tempted to use an if-statement. This is not necessary, and is not allowed.

Fortunately, Python gives us a very simple way to deal with this problem. The method find(a) returns -1 when the substring a is not found. Also note that s[:-1] leaves out the last character of the string. If you combine those two facts together, before_comma_or_end(s) should be just as simple as after_space(s).

To test the functions, you should make use of assert_equals in the module cornelltest to compare the result of each function with the string that you expect to get back. Our solution has four test cases for after_space and five test cases for before_comma_or_end. To give you an idea of what we are looking for after_space, consider the following questions:

  • Does the specification allow for strings with more than one space?
  • Does it allow for strings that start with a space?

The test cases for before_comma_or_end are very similar, except that they use commas instead of spaces.

Finally, do not forget to add a specification to testA(). Just because it is used for testing does not mean that it should not be properly specified.


Part B: Processing a JSON String

As we said in Part A, a JSON string is a sequence of key-value pairs. Our next step is to take a complete JSON string and extract a single-key value pair. For example, given the response JSON string

  '{"to": "EUR", "rate": 0.77203, "from": "USD", "v": 1.93007500000000}'

we want to extract the substring for key-value pair with key 'v'

  '"v": 1.88608072500000'

Note that the keys are always contained inside double quotes. If the value is a string, it is also contained inside double quotes. However, if the value is a number (like the example above), it is not.

The functions below are designed to help you with these key-value pairs. While working on each of the functions below, remember to write the test cases in a1test.py before implementing the body. All test cases in this section go in the procedure testB(), which you should remember to specify. You should thoroughly test each function before implementing the next one.


def get_keyword_index(json,key):
    """Returns: The index of the given key inside the JSON string 
    (including the quote character). It returns -1 if key is not there.
    
    A key is considered to be any substring inside of double quotes. For 
    example, in the string 
    
        'A "B" C'
    
    'B' is a key because it is inside double quote characters, while 'A'
    and 'C' are not.  However, arguments passed to the parameter key are 
    provided without any double quotes.
    
    Example: get_keyword_index('A "key" B','key') evaluates to 2.
    Example: get_keyword_index('A key B "key" C','key') evaluates to 8, 
    because it only chooses the instance of 'key' with quotes around it.
    Example: get_keyword_index('A x B "key" C','x') evaluates to -1, 
    because there is no instance of '"x"' in the string 'A x B "key" C'.
    Example: get_keyword_index('A "x" B','"x"') violates the precondition, 
    because the keyword argument is put in double quotes.
    
    Precondition: key is a string that does not contain double quotes, 
    while json is a string."""

Once you have this function completed, you should move on to the following functions.



def has_error(json):
    """Returns: True if json is an invalid response, and False otherwise.
    
    Given a JSON response to a currency query, this returns the boolean 
    value that tells whether the query is invalid. There are two ways for 
    the query to be invalid.  The first way is if the JSON response has 
    a warning message in it. Those strings have the form
    
    '{"to": <code>, "rate": <value>, "warning": <message>, "from": <code>}'

    For example, you enter an invalid number of dollars, you will get the 
    message
    
    '{"to": "EUR", "rate": 0.75, "warning": "invalid quantity, ignored.", "from": "USD"}'
    
    The second way a JSON can be an invalid response is if it has an error 
    message in it, like 
    
    '{"err": <description>}'
    
    For example, if you pass a query with invalid currency code, you will 
    get the message
    
    '{"err": "failed to parse response from xe.com."}'
    
    Be warned that both the warning and error messages can change; they are 
    not restricted to the examples above.  If there is neither an error 
    message nor a warning, this function returns False.
    
    Precondition: json is the response to a currency query."""


def get_value(json,key):
    """Returns: The JSON value (as a string) associated with the given key.

    The value is substring after the keyword but before  the first comma 
    (or the curly brace at the end). If the value is in double quotes, those 
    double quotes should be included as part of the substring.
    
    Example: get_value('{"to": "EUR", "rate": 0.75443, "from": "USD", "v": 1.88}','to')
    evaluates to '"EUR"'
    Example: get_value('{"to": "EUR", "rate": 0.75443, "from": "USD", "v": 1.88}','v')
    evaluates to '1.88' (still a string)
    
    Precondition: key is a string that does not contain double quotes.
    json is a JSON response string to a valid currency query, while key
    is a valid keyword in json."""


As always, write your unit tests before implementing the two functions. Look carefully at the specifications. You only need to test valid JSON queries. To get some JSON responses for testing, enter a query URL into the web service and copy the result into a test case.

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 must make use of the find() or index() string methods, plus the helper functions after_space(s) and before_comma_or_end(s).


Part C: Currency Query

Now it is time to interact with the web service. In this part, you will implement a single function. The test cases for this function should go in procedure testC() in a1test.py. Do not forget to specify testC() properly.


def currency_response(currency_from, currency_to, amount_from):
    """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
    
        '{"to": <new>, "rate": <exchange>, "from": <old>, "v": <amount>}'
    
    The values for "from" and "to" are the name for the original and 
    new currencies, respectively.  The value for "rate" is the exchange
    rate, while "v" is the value in the new currency.
    
    If the query is invalid, the JSON will have a keyword "err" 
    instead.
    
    Precondition: currency_from and currency_to are of type string,
    while amount_from is a float."""


While this function sounds complicated, it is not as bad as you think it is. You need to use the urlopen function from the module urllib2. This function takes a string that represents a URL and returns an instance of the class addinfourl 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.

Using one or both of these methods (you might not need them both) is enough to implement the function above.


Testing

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. For example, one test case can be constructed by seeing the result of going to the URL

http://cs1110.cs.cornell.edu/2014fa/a1/calculator.php?from=USD&to=EUR&q=2.5

Copy the value from this web page 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 simultaneously. You should give each call to this function at least 5-10 seconds to complete before restarting any tests.


Part D: Currency Exchange

We are now ready for the final part of the assignment. Implement the following specifications, again using our test-case-before-function-body approach. The test cases should go in procedure testD() in a1test, which you should properly specify. You may wish to use assert_true() instead of assert_equals() in some of your test cases. As with lab 3, there is also a case in which you will want to use assert_floats_equal().


def iscurrency(currency):
    """Returns: True if currency is a valid (3 letter code for a) currency. 
    It returns False otherwise.

    Precondition: currency is a string."""

In implementing iscurrency(), 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 the functions currency_response and has_error as helper functions.


def exchange(currency_from, currency_to, amount_from):
    """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 represents the amount in currency currency_to.

    The value returned has type float.

    Precondition: currency_from and currency_to are of type string,
    and are both valid three-letter currency codes.  The value
    amount_from is a float"""

Testing

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 the body of iscurrency() itself, it is okay to use the table to to decide on some test cases.

You may also use the table to craft some test cases for 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 in class: real numbers cannot be represented exactly. This creates problems when you try to test equality between floats. To solve this problem, cornelltest provides a function called assert_floats_equal(), which you encountered in 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 5 seconds to run the unit test on campus. This will be a bit slower if you are working closer to the deadline.


Finishing the Assignment

Once you have everything working you should go back and make sure that your program meets the class coding conventions, including the following:

  • There are no tabs in the file, only spaces.
  • Functions are each separated by two blank lines.
  • Lines are short enough (~80 characters) that horizontal scrolling is not necessary.
  • The specifications for all of the functions are complete.
  • Function specifications are immediately after the function header and indented.
  • Your name(s) and netid(s) are in the comments at the top of the modules.

One of the things that you may have the biggest difficulty with is breaking up long lines. First, you may not be aware when your lines are too long. There is an easy way to test. In Komodo Edit choose Edit > Preferences > Editor > Smart Edition. Select the checkbox that says Draw the edge line column.

As for breaking up long lines, there are two solutions. First, Python allows you to "hit Return" within any expression inside of parentheses. So if you are adding together several expressions together, like

  a = 'Hello ' + name + ', it is good to meet you'

you can break it up over several lines, using parentheses, as follows:

  a = ('Hello ' + name +
       ', it is good to meet you')

Another solution is to use the backslash symbol \. Remember that this is the escape character for making special characters in strings. It also has a special effect outside of a string. If you type this symbol, immediately followed by a return, then Python will know to continue to the next line. So you can rewrite the addition above as

  a = 'Hello ' + name + \
      ', it is good to meet you'

You can even use the backslash symbol inside of a string.

  a = 'Hello ' + name +    ', it is\
 good to meet you'

However, if you do the last trick, be very careful about how spaces are handled; you might accidentally introduce extra spaces on the second line.


Turning it In

Upload the files a1.py and a1test.py to CMS by the due date: Friday, September 19th 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 are brought to, click on the red word "show" in the line "Grading Comments & Requests (show)." 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, 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 Friday, September 19th, 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. You need to have submitted a final, correct version by Friday, September 26th, which means you will probably want to have re-submitted at least once before then.


Survey

In addition to turning in the assignment, we ask that you complete the new survey posted in CMS. These assignments are still rather new(ish), and we would like some understanding of 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.


Appendix: Connecting to Rate-Exchange Service

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://cs1110.cs.cornell.edu/2014fa/a1/calculator.php?
If you change that prefix to
  http://rate-exchange.appspot.com/currency?
you will use Rate-Exchange currency 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 being different from our currency table? That is one of the reasons we did not use the service; it is too hard to test against. In fact, even professional software engineers would do what we did: write a program against an unchanging exchange service before deploying it against the real thing.

If you end up using Rate-Exchange a lot, we ask that you buy the developer a coffee. Getting the currency exchange rates is not free, and the developer is hitting his quota with the increased load from this class.


Course Material Authors: D. Gries, L. Lee, S. Marschner, & W. White (over the years)