Week 10: Exercises

1. Stream Classes

Write a class Stream for reading lines of text, with subclasses StringStream and FileStream. Stream will be an abstract class: it does not make sense to create an instance of Stream, only of one of its subclasses.

On any Stream, it should be possible to call a method next_line() that reads the next input line and returns it as a string, or returns None at the end of the input. There should be a constructor StringStream(s) that takes a string containing data to read, and a constructor FileStream(name) that builds a FileStream that will read data from the given file. You will have separate implementations of next_line() in each of these subclasses.

The Stream class should also have a method next_non_empty() that reads lines until it finds a line that it non-empty, then returns it. If there are no more non-empty lines, the method should return None. You will need to implement this method only once.

2. Expression Interpreter

Write a program that reads and evaluates arithmetic expressions such as "22 + 31 - 4". In these expressions, the only supported arithmetic operators are "+" and "-", and parentheses are not allowed. Operators must be preceded and followed by writespace. Each time your program wants the user to enter an expression, it should print the prompt 'expr> ' and then wait for user input. The user can type 'exit' to exit the program. For example:

expr> 2 + 3
7
expr> 1 + 22 – 3
20
expr> exit

Your program should include a function eval() that takes a string containing an arithmetic expression and returns its value. If an expression is invalid (e.g. it contains a value that is not an integer, or includes an unsupported operator such as '*') then eval() should raise a InvalidExpression exception, which is a custom exception type that you should define. If an InvalidExpression exception is raised, then your program's top-level code should catch it, print an error message, then continue execution, prompting the user for the next expression. For example:

expr> 2 + 3
7
expr> 5 + b
Invalid expression
expr> 7 * 2
Invalid expression
expr> exit

3. Location Class

Write a class Location that represents a position on the Earth. The class should have an initializer that takes arguments 'lat' and 'long' representing a latitude and longitude in degrees. The initializer should check that the given latitude and longitude are valid, i.e. that -90 ≤ lat ≤ +90 and that -180 ≤ long ≤ +180. If that is not the case, it should raise an InvalidLocation exception, which is a custom exception type that you should define.

Your class should implement the magic method __eq__ so that two Locations with the same longitude and longitude will be considered equal. It should also implement the magic method __hash__, so that a Location may be used as a key in a set or dictionary.

Also, implement a magic method __sub__ that computes the shortest distance along the Earth's surface between two given locations. You may perform this calcluation using the Haversine formula:

Here φ1, φ2 are the latitude of points 1 and 2, and λ1, λ2 are the longitude of points 1 and 2. r is the radius of the sphere (i.e. the Earth); you may use the value r = 6371, which is the approximate radius of the Earth in km. Note that Python's functions math.sin and math.cos take an angle in radians, not degrees.

Finally, use your class to calculate the distance between Prague (lat = 50.0875°, long = 14.421389°) and Timbuktu (lat = 16.775833°, long = -3.009444°).