""" PRACTICE Exam 2, practice_problem 1. Authors: David Mutchler, Sana Ebrahimi, Mohammed Noureddine, Vibha Alangar, Matt Boutell, Dave Fisher, Mark Hays, their colleagues, and PUT_YOUR_NAME_HERE. """ # TODO: 1. PUT YOUR NAME IN THE ABOVE LINE. ############################################################################### # Students: # # These problems have DIFFICULTY and TIME ratings: # DIFFICULTY rating: 1 to 10, where: # 1 is very easy # 3 is an "easy" Exam 2 question. # 5 is a "typical" Exam 2 question. # 7 is a "hard" Exam 2 question. # 10 is an EXTREMELY hard problem (too hard for an Exam 2 question) # # TIME ratings: A ROUGH estimate of the number of minutes that we # would expect a well-prepared student to take on the problem. # # IMPORTANT: For ALL the problems in this module, # if you reach the time estimate and are NOT close to a solution, # STOP working on that problem and ASK YOUR INSTRUCTOR FOR HELP # on it, in class or via Piazza. ############################################################################### import math import time from numbers import Number import testing_helper ############################################################################### # TODO: 2. READ the Point class defined below. # Note especially its methods: # clone # distance_from # For full credit, you must use (call) these as appropriate in your code. # After you UNDERSTAND the SPECIFICATIONS of the methods and instance # variables (but not necessarily all of the code) of the Point class, # change the above _TODO_ to DONE. ############################################################################### class Point(object): """ Represents a point in 2-dimensional space. """ def __init__(self, x, y): """ Sets instance variables x and y to the given coordinates. """ self.x = x self.y = y def __repr__(self): """ Returns a string representation of this Point. For each coordinate (x and y), the representation: - Uses no decimal points if the number is close to an integer, - Else it uses 2 decimal places after the decimal point. Examples: Point(10, 3.14) Point(3.01, 2.99) """ decimal_places = 2 # Use 2 places after the decimal point formats = [] numbers = [] for coordinate in (self.x, self.y): if abs(coordinate - round(coordinate)) < (10 ** -decimal_places): # Treat it as an integer: formats.append('{}') numbers.append(round(coordinate)) else: # Treat it as a float to decimal_places decimal places: formats.append('{:.' + str(decimal_places) + 'f}') numbers.append(round(coordinate, decimal_places)) format_string = 'Point(' + formats[0] + ', ' + formats[1] + ')' return format_string.format(numbers[0], numbers[1]) def __eq__(self, p2): """ Defines == for Points: a == b is equivalent to a.__eq__(b). Treats two numbers as "equal" if they are within 6 decimal places of each other for both x and y coordinates. """ return (round(self.x, 6) == round(p2.x, 6) and round(self.y, 6) == round(p2.y, 6)) def clone(self): """ Returns a Point whose x and y are the same as this Point's.""" return Point(self.x, self.y) def distance_from(self, p2): """ Returns the distance this Point is from the given Point. """ dx_squared = (self.x - p2.x) ** 2 dy_squared = (self.y - p2.y) ** 2 return math.sqrt(dx_squared + dy_squared) ############################################################################### # The main function and the TODOs for you are after this: ############################################################################### def main(): """ Calls the TEST functions in this module. """ # ------------------------------------------------------------------------- # Uncomment the following calls to the testing functions one at a time # as you work the problems. # ------------------------------------------------------------------------- print() print('Un-comment the calls in MAIN one by one') print(' to run the testing code as you complete the TODOs.') # run_test_init() # run_test_area() # run_test_bigger_triangle() # run_test_shrink_or_expand() # run_test_return_doubled_triangle() # run_test_get_largest_area() ############################################################################### # The Triangle class (and its methods) begins here. ############################################################################### class Triangle(object): """ Represents a triangle in 2-dimensional space. """ # IMPORTANT: Be CERTAIN you understand the Example in the spec below. def __init__(self, a, b, c): """ What comes in: -- self and three Point objects where the three Point objects are to be the initial points of this Triangle. What goes out: Nothing (i.e., None). Side effects: Sets instance variables: self.a self.b self.c to CLONES of the three Point objects a, b, and c. Also, initializes other instance variables as needed by other Triangle methods. Example: This __init__ method runs when one constructs a Triangle. So the fourth of the following statements invokes the __init__ method of this Line class: p1 = Point(30, 17) p2 = Point(50, 80) p3 = Point(35, 15) triangle = Triangle(p1, p2, p3) # Causes __init__ to run print(triangle.a) # Should print Point(30, 17) print(triangle.b) # Should print Point(50, 80) print(triangle.c) # Should print Point(35, 15) print(triangle.a == p1) # Should print True print(triangle.a is p1) # Should print False Type hints: :type a: Point :type b: Point :type c: Point """ # --------------------------------------------------------------------- # TODO: 3. # a. READ the above specification, including the Example. # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # --------------------------------------------------------------------- # IMPORTANT: Read the HINT in the specification below. def area(self): """ What comes in: -- self What goes out: Returns the area of this Triangle. Side effects: None. HINT: Recall Heron's formula for the area of a triangle: Area = square root of (S * (S - length of side 1) * (S - length of side 2) * (S - length of side 3)) where S = (1/2) * (length of perimeter of the triangle) For example: if the triangle has endpoints: a = Point(15, 35) b = Point(15, 50) c = Point(35, 45) then one can compute (** using the Point distance_from method **) that: the length of the side from a to b is (about): 15 the length of the side from b to c is (about): 20.6 the length of the side from c to a is (about): 22.4 and hence S is (about) (1/2) * (15 + 20.6 + 22.4), which is about 28.99 and so the area of the Triangle is (per the formula): 150.0 Type hints: :rtype: float """ # --------------------------------------------------------------------- # TODO: 4. # a. READ the above specification, including the Example AND HINT! # __ # ************* READ THE HINT!!!!!!!!! ************* # __ # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # --------------------------------------------------------------------- # IMPORTANT: CALL the area method in your solution to the following. def bigger_triangle(self, triangle2): """ What comes in: -- self -- another Triangle object What goes out: returns True if this Triangle has a larger area than the given Triangle (triangle2). Returns False otherwise. Side effects: None. Type hints: :type: triangle2: Triangle :rtype: bool """ # --------------------------------------------------------------------- # TODO: 5. # a. READ the above specification, including the Example. # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # --------------------------------------------------------------------- # IMPORTANT: In the function below, do NOT return a Triangle. Mutate it. def shrink_or_expand(self, f): """ What comes in: -- self -- a positive number f What goes out: Nothing. Side effects: MUTATES this Triangle object by multiplying the x and y coordinates of each of this Triangle's three points by the given number f. Type hints: :type: f: float """ # --------------------------------------------------------------------- # TODO: 6. # a. READ the above specification, including the Example. # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # --------------------------------------------------------------------- def return_doubled_triangle(self): """ What comes in: -- self What goes out: -- Return a new Triangle object that is the same as this Triangle object, except that the x and y coordinates of its a, b, and c endpoints are each TWICE the values of this Triangle's a, b and c. Side effects: None. Type hints: :rtype: Triangle: """ # ------------------------------------------------------------------------- # TODO: 7. # a. READ the above specification, including the Example. # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # ------------------------------------------------------------------------- def get_largest_area(self): """ What comes in: -- self What goes out: -- Returns the area of the Triangle when it was the largest during any shrink_or_expand operations. Returns the initial area of the Triangle if there have been no shrink_or_expand operations so far. Side effects: None. Type hints: :rtype: float: """ # --------------------------------------------------------------------- # TODO: 8. # a. READ the above specification, including the Example. # ** ASK QUESTIONS AS NEEDED. ** # ** Be sure you understand it, ESPECIALLY the Example. # b. Implement and test this method. # The tests are already written (below). # They include the Example in the above doc-string. # --------------------------------------------------------------------- ############################################################################### # The TEST functions for the Triangle class begin here. ############################################################################### def run_test_init(): """ Tests the __init__ method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the __init__ method of the Triangle class.') print('-----------------------------------------------------------') # Test 1 print('\nTest 1:') p1 = Point(0, 0) p2 = Point(5, 2) p3 = Point(3, 6) t1 = Triangle(p1, p2, p3) expected_a = Point(0, 0) expected_b = Point(5, 2) expected_c = Point(3, 6) run_test_instance_variables(t1, expected_a, expected_b, expected_c) if (p1 is t1.a) or (p2 is t1.b) or (p3 is t1.c): print_failure_message( '\nFAILED CLONING: You failed to CLONE the arguments.\n' + 'See your instructor for help.\n') # Test 2 print('\nTest 2:') p1 = Point(10, 10) p2 = Point(15, 34) p3 = Point(45, 100) t2 = Triangle(p1, p2, p3) expected_a = Point(10, 10) expected_b = Point(15, 34) expected_c = Point(45, 100) run_test_instance_variables(t2, expected_a, expected_b, expected_c) if (p1 is t1.a) or (p2 is t1.b) or (p3 is t1.c): print_failure_message( '\nFAILED CLONING: You failed to CLONE the arguments.\n' + 'See your instructor for help.\n') def run_test_area(): """ Tests the area method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the area method of the Triangle class.') print('-----------------------------------------------------------') p1 = Point(15, 35) p2 = Point(15, 50) p3 = Point(35, 45) t1 = Triangle(p1, p2, p3) print() print('Expected for area:', 150.0) print('Actual: ', t1.area()) print_result_of_test(150.0, t1.area()) p4 = Point(25, 40) p5 = Point(15, 50) p6 = Point(35, 45) t2 = Triangle(p4, p5, p6) print() print('Expected for area:', 75.0) print('Actual: ', t2.area()) print_result_of_test(75.0, t2.area()) p7 = Point(15, 20) p8 = Point(25, 10) p9 = Point(35, 20) t3 = Triangle(p7, p8, p9) print() print('Expected for area:', 100.0) print('Actual: ', t3.area()) print_result_of_test(100.0, t3.area()) def run_test_bigger_triangle(): """ Tests the bigger_triangle method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the bigger_triangle method of the Triangle class.') print('-----------------------------------------------------------') p1 = Point(15, 35) p2 = Point(15, 50) p3 = Point(35, 45) t1 = Triangle(p1, p2, p3) p4 = Point(40, 45) t2 = Triangle(p1, p2, p4) print() print('Expected for bigger_triangle:', True) print(' Actual:', t2.bigger_triangle(t1)) print_result_of_test(True, t2.bigger_triangle(t1)) print() print('Expected for bigger_triangle:', False) print(' Actual:', t1.bigger_triangle(t2)) print_result_of_test(False, t1.bigger_triangle(t2)) print() print('Expected for bigger_triangle:', False) print(' Actual:', t1.bigger_triangle(t1)) print_result_of_test(False, t1.bigger_triangle(t1)) p5 = Point(15, 20) p6 = Point(25, 10) p7 = Point(35, 20) t3 = Triangle(p5, p6, p7) p8 = Point(25, 40) p9 = Point(15, 50) p10 = Point(35, 45) t4 = Triangle(p8, p9, p10) print() print('Expected for bigger_triangle:', False) print(' Actual:', t4.bigger_triangle(t3)) print_result_of_test(False, t4.bigger_triangle(t3)) print() print('Expected for bigger_triangle:', True) print(' Actual:', t3.bigger_triangle(t4)) print_result_of_test(True, t3.bigger_triangle(t4)) print() print('Expected for bigger_triangle:', True) print(' Actual:', t1.bigger_triangle(t3)) print_result_of_test(True, t1.bigger_triangle(t3)) print() print('Expected for bigger_triangle:', True) print(' Actual:', t1.bigger_triangle(t4)) print_result_of_test(True, t1.bigger_triangle(t4)) def run_test_shrink_or_expand(): """ Tests the shrink_or_expand method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the shrink_or_expand method of the Triangle class.') print('-----------------------------------------------------------') # Test 1 print('\nTest 1:') p1 = Point(10, 20) p2 = Point(18, 26) p3 = Point(30, 10) t1 = Triangle(p1, p2, p3) t1.shrink_or_expand(0.5) expected_a = Point(5, 10) expected_b = Point(9, 13) expected_c = Point(15, 5) run_test_instance_variables(t1, expected_a, expected_b, expected_c) p1 = Point(20, 50) p2 = Point(10, 30) p3 = Point(5, 60) t2 = Triangle(p1, p2, p3) t2.shrink_or_expand(3) expected_a = Point(60, 150) expected_b = Point(30, 90) expected_c = Point(15, 180) run_test_instance_variables(t2, expected_a, expected_b, expected_c) def run_test_return_doubled_triangle(): """ Tests the return_doubled_triangle method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the return_doubled_triangle method of the Triangle class') print('-----------------------------------------------------------') p1 = Point(30, 75) p2 = Point(15, 45) p3 = Point(30, 90) t1 = Triangle(p1, p2, p3) t2 = t1.return_doubled_triangle() print() print("Testing whether the correct Triangle was returned:") expected_a = Point(60, 150) expected_b = Point(30, 90) expected_c = Point(60, 180) run_test_instance_variables(t2, expected_a, expected_b, expected_c) print() print("Testing that the Triangle itself was not mutated:") run_test_instance_variables(t1, p1, p2, p3) def run_test_get_largest_area(): """ Tests the get_largest_area method of the Triangle class. """ print() print('-----------------------------------------------------------') print('Testing the get_largest_area method of the Triangle class.') print('-----------------------------------------------------------') p1 = Point(5, 5) p2 = Point(12, 8) p3 = Point(5, 9) t1 = Triangle(p1, p2, p3) area1 = t1.area() print() print('Expected for get_largest_area:', area1) print(' Actual:', t1.get_largest_area()) print_result_of_test(area1, t1.get_largest_area()) t1.shrink_or_expand(6) area2 = t1.area() print() print('Expected for get_largest_area:', area2) print(' Actual:', t1.get_largest_area()) print_result_of_test(area2, t1.get_largest_area()) t1.shrink_or_expand(0.1) area3 = t1.area() print() print('Expected for get_largest_area:', area2) print(' Actual:', t1.get_largest_area()) print_result_of_test(area2, t1.get_largest_area()) p1 = Point(5, 5) p2 = Point(12, 8) p3 = Point(5, 9) t2 = Triangle(p1, p2, p3) area4 = t2.area() print() print('Expected for get_largest_area:', area4) print(' Actual:', t2.get_largest_area()) print_result_of_test(area4, t2.get_largest_area()) print() print('Expected for get_largest_area:', area2) print(' Actual:', t1.get_largest_area()) print_result_of_test(area2, t1.get_largest_area()) ############################################################################### # The following are HELPER functions that display error messages in RED # and help make it easier for us to write tests. # Do NOT change any of the following. ############################################################################### def run_test_instance_variables(triangle, expected_a, expected_b, expected_c): """ Tests whether the instance variables for the given Triangle are per the given expected values. -- Prints relevant messages. -- Returns True if all is OK, else returns False. """ try: return (run_test_type_of_object(triangle) and run_test_types_of_instance_variables(triangle) and run_test_values_of_instance_variables( triangle, expected_a, expected_b, expected_c)) except Exception: something_unexpected_happened_in_our_testing_code() return False def run_test_values_of_instance_variables(triangle, expected_a, expected_b, expected_c): # Print the EXPECTED and ACTUAL values of the instance variables format_string = ' {:9} {:15} {:15} {:15}' print(' Testing instance variables:') print(' a b c') print(' ------ ------ -------') print(format_string.format('Expected:', str(expected_a), str(expected_b), str(expected_c))) print(format_string.format('Actual:', str(triangle.a), str(triangle.b), str(triangle.c))) # Print a message indicating whether or not # the EXPECTED values are equal to the ACTUAL values. expected = (expected_a, expected_b, expected_c) actual = (triangle.a, triangle.b, triangle.c) return print_result_of_test(expected, actual) def something_unexpected_happened_in_our_testing_code(): print_failure_message() explanation = ( ' Something unexpected has happened in the testing \n' + ' code that we supplied. You should probably\n' + ' SEEK HELP FROM YOUR INSTRUCTOR NOW.') print_failure_message(explanation) def run_test_type_of_object(triangle): """ Returns True if the argument is in fact a Triangle object """ if isinstance(triangle, Triangle): return True else: explanation = (' The following object to test:\n' + ' ' + str(triangle) + '\n' + ' should be a Triangle object,\n' + ' but it is not. Perhaps your code\n' + ' returned something of the wrong type?') print_failure_message() print_failure_message(explanation) return False def run_test_types_of_instance_variables(triangle): """ Returns True if the argument has the right instance variables and they are all numbers. """ # If NONE of the expected instance variables exist, # then perhaps the only "problem" is that the __init__ method # has not yet been implemented. attributes = dir(triangle) if ('a' not in attributes and 'b' not in attributes and 'c' not in attributes): explanation = ( ' This object:\n' + ' ' + str(triangle) + '\n' + ' should have these instance variables:\n' + ' self.a\n' + ' self.b\n' + ' self.c\n' + ' but it has NONE of them.\n' + ' Perhaps you simply have not yet\n' + ' implemented the __init__ method?\n' + ' (If so, implement it now.)') print_failure_message() print_failure_message(explanation) return False # If SOME (but not all) of the expected instance variables exist, # then perhaps something was misspelled in __init__. if not ('a' in attributes and 'b' in attributes and 'c' in attributes): explanation = ( ' This object:\n' + ' ' + str(triangle) + '\n' + ' should have these instance variables:\n' + ' self.a\n' + ' self.b\n' + ' self.c\n' + ' but it is missing some of them.\n' + ' Perhaps you misspelled something\n' + ' in your __init__ code?') print_failure_message() print_failure_message(explanation) return False # Check that the instance variables are of the right types: # if not isinstance(cloud.capacity, str): # explanation = ( # ' This object:\n' + # ' ' + str(cloud) + '\n' + # ' has an instance variable capacity with this value:\n' + # ' capacity: ' + str(cloud.capacity) + # ' That value should be a STRING, but is isn\'t.\n') # print_failure_message() # print_failure_message(explanation) # return False # # if not isinstance(cloud.water, list): # explanation = ( # ' This object:\n' + # ' ' + str(cloud) + '\n' + # ' has an instance variable water with this value:\n' + # ' water: ' + str(cloud.water) + # ' That value should be a LIST, but is isn\'t.\n') # print_failure_message() # print_failure_message(explanation) # return False # # if not is_list_of_strings(cloud.water): # explanation = ( # ' This object:\n' + # ' ' + str(cloud) + '\n' + # ' has an instance variable water with this value:\n' + # ' water: ' + str(cloud.water) + # ' That value should be a list of STRINGS, but is isn\'t.\n') # print_failure_message() # print_failure_message(explanation) # return False return True def is_list_of_strings(strings): return ((strings == []) or (isinstance(strings[0], str) and is_list_of_strings(strings[1:]))) def print_result_of_test(expected, actual): if are_equal(expected, actual): print(" PASSED the above test -- good!", color="blue") return True print_failure_message() if isinstance(expected, list) or isinstance(expected, tuple): explanation = ( ' For at least one of the above, its Expected value\n' + ' does not equal its Actual value.') # Note: the printed\n' + # ' values are the actual values ROUNDED to 1 decimal place.') print_failure_message(explanation) return False def are_equal(a, b): # We will treat two numbers as being "equal" if they are # the same when each is rounded to 12 decimal places. if isinstance(a, Number) and isinstance(b, Number): return (round(a, 12) == round(b, 12)) # For lists and tuples, their items have to be equal for the # lists/tuples to be equal. if isinstance(a, list) and isinstance(b, list): if len(a) != len(b): return False for k in range(len(a)): if not are_equal(a[k], b[k]): return False return True # All the items were equal. if isinstance(a, tuple) and isinstance(b, tuple): if len(a) != len(b): return False for k in range(len(a)): if not are_equal(a[k], b[k]): return False return True # All the items were equal. # For all else, they must be equal in the "usual" way: return a == b def print_failure_message(message=' *** FAILED the above test. ***', flush_time=0.5): """ Prints a message onto stderr, hence in RED. """ time.sleep(flush_time) print(message, flush=True, color="red") time.sleep(flush_time) # To allow color-coding the output to the console: USE_COLORING = True # Change to False to revert to OLD style coloring testing_helper.USE_COLORING = USE_COLORING if USE_COLORING: # noinspection PyShadowingBuiltins print = testing_helper.print_colored else: # noinspection PyShadowingBuiltins print = testing_helper.print_uncolored # ----------------------------------------------------------------------------- # Calls main to start the ball rolling. # The try .. except prevents error messages on the console from being # intermingled with ordinary output to the console. # ----------------------------------------------------------------------------- try: main() except Exception: print('ERROR - While running this test,', color='red') print('your code raised the following exception:', color='red') print() time.sleep(1) raise