Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

CS 112 – Project 6 Classes, Exceptions Due Date: SUNDAY, May 7 th , 11:59pm Note

ID: 3830595 • Letter: C

Question

CS 112 – Project 6                                                                                                                 Classes, Exceptions

Due Date: SUNDAY, May 7th, 11:59pm

Note: no late submissions allowed on this project! Make the deadline, plan ahead. Please read the "Notes" section before writing any code!

The purpose of this assignment is to explore classes and exceptions. We will be creating classes to represent books, patrons, and libraries, and then implementing some methods to capture the interactions available between them.

You will turn in a single python file following our naming convention (example: gmason76_2XX_P6.py)

Similar to previous projects, include your name, G#, Lecture/Lab sections, and any extra comments we ought to know, as a comment at the top of your file.

If you have questions, use Piazza (and professor/TA office hours) to obtain assistance.

Remember, do not publicly post code for assignments on the forum! Ask a general question in public, or ask a private question (addressed to all "instructors") when you're asking about your particular code. Also please have a specific question; instead of "my code doesn't work, please help", we need to see something like "I'm having trouble when I add that particular line, what am I misunderstanding?". If you are unsure whether a question may be public or not, just mark it as private to be sure. We can change a post to public afterwards if it could have been public.

Background

Classes allow us to define new types for Python. We can first think of a class as defining a new container – instead of a list, tuple, set, or dictionary, we can have our own collection of values, each with a chosen name. We can then read out a value or update a value, much like reading or replacing the values in a list or dictionary. But we can also put methods in a class definition, giving us a way to specify the exact ways we should interact with values of this new type.

Once we have created a class definition, we can create as many objects of the new type as we want and use them in our programs. We can create entirely new types during the design phase of writing a program. This enables us to think in terms of types (and instances of types) that better model what we see in the real world, instead of endlessly relying on lists or dictionaries (and having to remember exactly how we intended to use those things as proxies for the values we actually had in mind).

Exceptions allow us to recover from unusual events – some unavoidable (user types in bad file name or bad input in general), some unexpected (such as IndexErrors when stepping through a list). Without exceptions, we tend to program in an "ask permission, then attempt" style of calculation. But using exceptions, we can instead program in a "try it first, beg for forgiveness" approach by writing code that specifies how to recover from exceptions that we allowed to occur.

What's Allowed?

We've finally learned a substantial bit of programming! On this assignment, don't import anything except math; other than that you should be able to use any built-ins that you want; the project focuses on class structure, so there's not likely to be things that would make for any shortcuts.

Procedure

Complete the class definitions as described; you can test your code with the included testing file:

Invoke it as with prior assignments:   python3     tester6p.py yourcode.py

You can also test individual parts of the code, but note that you either want to type a class name or the name of a batch of test cases. You can either use the class name to test __init__, __str__, and __eq__ (any 'basic functionality' tests), or the other method names from Train, City, and Journey classes: python3 tester6p.py yourcode.py Train       python3     tester6p.py yourcode.py unload_passengers

                                                  python3 tester6p.py yourcode.py Train City   Journey                           add_destination   …

o      In addition to method and class names, these three names can be used to test exception behavior of relevant methods/classes: unload_passengers_exception, load_passengers_exception, and journey_type_error.    

• You can also run your code in interactive mode: python3    –i    yourcode.py

Scenario

You will create world with cities, trains, and exciting journeys! This will require some proscribed class definitions. Create all the classes as defined below, all in your single .py file. Some of the classes build upon each other – this is an example of aggregation. Many of the methods required below are very regular – after you've seen one __init__ method on this program, you've sort of seen them all. While there might be a bit more typing than usual, the amount of mental work to "solve the puzzle" should be a bit minimal once you get used to the patterns.

Classes

You will be writing four classes (Train, City, Journey, and TrainCapacityException). Make sure you understand what these represent and how they relate to each other before continuing.

Train class:

Instance variables:

                   • name                        :: string

#      the    name   of     the    train

                  • max_passengers         :: int       

#      the    maximum      number of     passengers   the       train can    transport

                  • num_passengers      :: int  

#      the    number of     passengers   currently    on       the    train

                  • speed_fps              :: int   

Methods:

#      how    fast   the    train travels      (in    feet       per    second)

__init__(self,     name, max_passengers,     speed_fps) :: constructor, initializes all instance variables, num_passengers should default to 0 when a train is created with the constructor. o Assumptions:

§ speed_fps and max_passengers will always be a non-negative number

§ name will be a non-empty string

__str__(self) :: returns string representation with this format:

"Train    named Orient Express      with   5      passengers   will   travel at    10.20mph"   

o Notes:

The quotes in the example are just our string boundaries! The first character in the above example is T.

This method reports num_passengers (NOT max_passengers).

Train speed is converted to mile per hour and always presented with 2 digits after the decimal point.

time_to_travel(self,   distance_feet) :: Returns how long (in whole seconds) this train will take to travel distance_feet (distance in feet). o Notes and Assumptions:

This always returns an integer – you could use integer division (//) or normal division followed by int() conversion to truncate the fractional part.

You can assume that distance_feet is always a non-negative integer.

load_passengers(self, num_people) :: Attempts to put num_people onto the train and update num_passengers. If the number of people supplied would not fit on the train, raise a

TrainCapacityException and do not change the number of passengers currently on the train. [You may imagine this as “the passengers get into a fight and no one is allowed on the train”.] o Note and Assumptions:

You can assume that num_people is always a non-negative integer.

When raising the exception, the number to raise is the number of people that cannot fit on the train (not the number of people that try to board).

This function should return None. Remember: None is the default return if you don’t return anything! You don’t need to explicitly return None.

unload_passengers(self,       num_people) :: Attempts to remove num_people from the train. If the number of people supplied is larger than the number of people on the train currently, raise a TrainCapacityException and do not change the number of passengers currently on the train. [You may imagine this as “the passengers are so confused by the request that they just stay where they are looking confused”.] o Note and Assumptions:

You can assume that num_people is always a non-negative integer.

When raising the exception, the number to raise is the number of people that cannot unload, not the number of people that try to exit the train.

This function should return None. Remember: None is the default return if you don’t return anything! You don’t need to explicitly return None.

TrainCapacityException class:

By specifying the parent class Exception as in the example below, objects (values) of this class can be raised and caught.

                         class    TrainCapacityException(Exception):     

Instance variables:   

number

:: int    

#      how    many   people can’t fit    or     be       unloaded     from   the    train

issue

:: string

#      should be     either "full" or     "empty"

      

  

             

#      "full" indicates    that   the    train was    full             

      

  

             

#      "empty"      indicate     the    error was    due       to     the    train being empty

  

Methods:

__init__(self, number,      issue="full") :: constructor; instantiates all instance variables. You can assume all incoming arguments are good and no need to perform any checking.

__str__(self) :: returns string representation matching this format (based on number and issue):

o '5     passengers cannot      be    loaded      because   the   train is    full!' o '5 passengers cannot      be   unloaded    because     the   train is    empty!'

City class:

Instance variables:

                   • name           

:: string

#      the    name   of     the    city

                  • loc_x                       

:: int    

#      the    city’s x      location     (in    an     x-y-coordinate   system)

                  • loc_y         

:: int  

#      the    city’s y      location     (in    an     x-y-coordinate   system)

stop_time  

Methods:

:: int    

#      how    long   (in    number of     seconds)     trains       stop   in     this   city

__init__(self, name, loc_x, loc_y, stop_time) :: constructor, initializes all instance variables.

o Assumption: All incoming arguments are good; there is no need to perform any checking.

__str__(self) :: returns string representation with this format (note, the quotes in the example are just our quote boundaries! The first character in this example string is N):

"New      York   (0,3). Exchange     time: 5.00   minutes" o Notes:

The (0,3) in the example above are the coordinates of the city.

Stop time is converted to number of minutes with 2 digits after the decimal point.

__eq__(self,     other) :: Override the == method for City objects; returns True or False. Cities should be “equal” to each other if they are at the same location (loc_x and loc_y), regardless of their name or stop time. For example, City("New York", 0,     0,     0) and City("The     Big   Apple",      0,     0,     10) would be the same since they are physically located in the same location.

distance_to_city(self, city) :: Returns the distance between to cities (based on their x and y locations) as a float (no truncating). Remember: a2 + b2 = c2!

Journey class:

Instance variables:

train        

:: Train                       

#      the    train making the    journey

destinations

:: list of City objects

#      the    cities the    train will       visit, in     order

start_time

:: int                            

#      the    time   the    train will   begin       its    journey            

                    

                          

#      (in    seconds      since January       1st,   1970)

Note: Why Thursday, Jan. 1st 1970? See: https://en.wikipedia.org/wiki/Unix_time

Methods:

__init__(self, train, destinations=None, start_time=0) :: constructor, initializes all instance variables. When no list of Cities is supplied, this creates an empty list as destinations. This should raise a TypeError if (a) train is not of type Train, (b) destinations is not of type list, or (c) destinations contains anything other than instances of City.

o Hint: Use the type() function to determine the type! (This was introduced near the beginning of the semester.)

__str__(self) :: returns string representation with this format (when printed out):

Journey   with   3      stops:

                                             Philadelphia (0,0). Exchange     time:   30.00 minutes     

                                             New    York   (0,22).      Exchange   time: 10.00 minutes     

                                             Boston (22,22).     Exchange     time: 5.00   minutes     

Train     Information: Train named Orient Express      with   5      passengers   will travel at     15.00mph     o Notes:

Each city in the destinations is presented on a separate line, starting with a tab

The train information is the last line and ends with a newline o Hints:

You should reuse the string formatting for Train and City objects instead of defining them again!

Look at the sample run (and tester file) for more examples!

add_destination(self, city) :: Appends a city to the list of destinations and returns None. Assumption: You may assume city is an instance of City.

city_in_journey(self, city) :: Determines if a city is one of the destinations in the journey. Returns a boolean. Assumption: You may assume city is an instance of City.

check_journey_includes(self, start_city, dest_city) :: Check if the journey will include travel from the start_city to the dest_city. Stops are acceptable in between the two cities. Returns a boolean.Hints and Assumptions:

There are many ways to solve this problem, but the list’s index() method might be helpful here. You now know how to handle the exceptions it might raise!

You may assume cities are instances of City.

total_journey_distance(self) :: Returns the total distance traveled (in feet as a float).

city_arrival_time(self,       city) :: Returns the time at which the train will arrive at a given city. It returns an integer -- whole seconds since January 1st, 1970 (similar to how we specify start_time of the journey). Returns None if city is not included in journey. If the city is visited more than once on the journey, the first time the city is visited should be returned. You may assume city is an instance of City. The arrival time can be computed by:

                                       Stop Time at         Journey Time        Stop Time at                 …             

                                                     Start City           to Second City        Second City

Start City arrival Start City 2nd City 2nd City Arrival at City time (start_time) departure time arrival time departure time of Interest

city_departure_time(self,     city) :: Returns the time at which the train will leave the given city. It returns an integer -- whole seconds since January 1st, 1970. Returns None if city is not included in journey. If the city is visited more than once on the journey, the last time the train leaves the city should be returned. You may assume city is an instance of City.

total_journey_time(self) :: Returns the total time taken on the journey (in seconds). This is the departure time of the last city minus the start time of the journey.

all_passengers_accommodated(self,   unload_list, load_list) :: Given a list with the number of passengers unloading at each city, and a list of the number of passengers boarding the train at each city, determine if all passengers can be accommodated (i.e. they will all be able to board / leave as indicated in the lists). For example:

Unload    List: [0,1,2],     Load   List: [1,2,0]

      

#at    the    first city, unload:      0      passengers   will   try    to exit   the    train                             load: 1      passenger    will try    to     board the    train

#at    the    second city, unload:      1      passenger    will   try    to exit   the    train                             load: 2      passengers   will try    to     board the    train

#at    the    third city, unload:      2      passengers   will   try    to     exit   the train                                           load: 0      passengers   will   try    to board the    train

Notes:

You must ask the train of the journey to load_passengers or unload_passengers. This will adjust the num_passengers on the train.

Exceptions might be raised via the code from the Train class; you must handle these exceptions and return the required boolean as part of your exception handling.

The train may or may not start with passengers already on the train at the first city. § At the end, the number of people on the train may be non-zero.

Assumptions:

The length of unload_list and load_list will be the same as each other and both will be equal to the size of the destinations list for the journey.

The unload_list and load_list will never contain invalid values (such as negative numbers), but they might contain 0s.

Grading Rubric

      

Code passes     shared     tests:                          95%       

Well-documented/submitted:                 10%

---------------------------------    

TOTAL:                                                                                                                     105%            extra credit is    baked in!  

No globals allowed – you'll be penalized if you use them!

There are five extra points available rather than a specific set of extra credit points. Earn as many as you can from anywhere and get a good score to finish the semester strong!

                   • name                        :: string

#      the    name   of     the    train

                  • max_passengers         :: int       

#      the    maximum      number of     passengers   the       train can    transport

                  • num_passengers      :: int  

#      the    number of     passengers   currently    on       the    train

                  • speed_fps              :: int   

Methods:

#      how    fast   the    train travels      (in    feet       per    second)

Explanation / Answer

import math

class TrainCapacityException(Exception):
def __init__(self, number, issue="full"):
self.number = number
self.issue = issue
def __str__(self):
if self.issue == "full":
return str(self.number) + " passengers cannot be loaded because the train is full!"
else:
return str(self.number) + " passengers cannot be unloaded because the train is empty!"

class Train:
def __init__(self, name, max_passengers, speed_fps):
self.name = name
self.max_passengers = max_passengers
self.num_passengers = 0
self.speed_fps = speed_fps
def __str__(self):
train = "Train named " + self.name + " with " + str(self.num_passengers)
speed_mps = 0.681818*self.speed_fps
train += " passengers will travel at " + "{0:.2f}".format(speed_mps) + "mph"
return train
  
def time_to_travel(self, distance_feet):
return distance_feet//self.speed_fps
  
def load_passengers(self, num_people):
if self.num_passengers + num_people <= self.max_passengers:
self.num_passengers += num_people
else:
over_capacity = (self.num_passengers + num_people) - self.max_passengers
raise TrainCapacityException(over_capacity)
  
def unload_passengers(self, num_people):
if self.num_passengers - num_people >= 0:
self.num_passengers -= num_people
else:
under_capacity = num_people - self.num_passengers
raise TrainCapacityException(under_capacity, "empty")

class City:
def __init__(self, name, loc_x, loc_y, stop_time):
self.name = name
self.loc_x = loc_x
self.loc_y = loc_y
self.stop_time = stop_time
def __str__(self):
result = self.name + " (" + str(self.loc_x) + "," + str(self.loc_y) + "). "
minutes = self.stop_time/60.0
result += "Exchange time: " + "{0:.2f}".format(minutes) + " minutes"
return result
  
def __eq__(self, other):
return (self.loc_x == other.loc_x) and (self.loc_y == other.loc_y)
  
def distance_to_city(self, city):
distance = math.sqrt((self.loc_x - city.loc_x)**2 + (self.loc_y - city.loc_y)**2)
return distance

class Journey:
def __init__(self, train, destinations=None, start_time=0):
  
if not isinstance(train, Train):
raise TypeError("train should be instance of Train")
  
self.train = train
if not destinations:
self.destinations = []
else:
if not type(destinations) is list:
raise TypeError("destinations should be list")
for city in destinations:
if not isinstance(city, City):
raise TypeError("city should be instance of City")
self.destinations = destinations
  
self.start_time = start_time

def city_departure_time(self, city):
if not city in self.destinations:
return
time = self.start_time
departure_time = time
for i in range(1, len(self.destinations)):
time = time + self.destinations[i-1].stop_time
if city == self.destinations[i-1]:
departure_time = time
time = time +int((self.destinations[i].distance_to_city(self.destinations[i-1]))/self.train.speed_fps)
  
if self.destinations[-1] == city:
departure_time = time + city.stop_time
  
return departure_time
  
def city_arrival_time(self, city):
if not city in self.destinations:
return
time = self.start_time
arrival_time = time
  
for i in range(1, len(self.destinations)):
if self.destinations[i-1] == city:
return arrival_time
arrival_time = arrival_time + int((self.destinations[i].distance_to_city(self.destinations[i-1]))/self.train.speed_fps) + self.destinations[i-1].stop_time
if self.destinations[-1] == city:
return arrival_time
  
return
  
def add_destination(self,c):
self.destinations.append(c)
  
def city_in_journey(self,c):
return c in self.destinations
  
def check_journey_includes(self, start_city, dest_city):
found_start = False
for dest in self.destinations:
if found_start and dest == dest_city:
return True
if dest==start_city:
found_start = True
return False
  
def total_journey_time(self):
if not self.destinations:
return 0
return self.city_departure_time(self.destinations[-1]) - self.start_time
  
def total_journey_distance(self):
if len(self.destinations) < 2:
return 0
distance = 0
for i in range(1, len(self.destinations)):
distance += self.destinations[i].distance_to_city(self.destinations[i-1])
return distance
  
def all_passengers_accommodated(self, unload_list, load_list):
for i in range(0, len(self.destinations)):
if unload_list[i] != 0:
try:
self.train.unload_passengers(unload_list[i])
except:
return False
if load_list[i] != 0:
try:
self.train.load_passengers(load_list[i])
except:
return False
return True
  
def __str__(self):
result = "Journey with " + str(len(self.destinations)) + " stops: "
for destination in self.destinations:
result += " " + str(destination) + " "
result += "Train Information: " + str(self.train) + " "
return result

# pastebin link for code : https://pastebin.com/rc817r8D