""" This module REVISITS the simple Line class that you implemented previously. Note that: 1. This is NOT rosegraphics -- it is your OWN Line class. 2. We supply (below) a Point class that you should use in implementing your Line class. YOU will implement PART of the Point class. The learning objective of this exercise is to review QUICKLY the key ideas of implementing classes, by RE-DOING work that you did previously, but doing it in a BETTER way, by following along with your instructor. The KEY IDEAS are: 1. The notation for implementing a class called (say) Blah is: class Blah: def __init__(self, ...): ... def other_methods(self, ...): ... 2. When one constructs an instance of class Blah, e.g.: b = Blah(...) ** the __init__ method in Blah's class definition runs, ** with self referring to a NEW place in memory for ** Blah object being constructed ** and the remaining arguments referring to the values in the parentheses. 3. When one calls a method on an instance of class Blah, e.g.: b.other_method(...) the other_method method in Blah's class definition runs, with self referring to ** the object in front of the DOT ** and the remaining arguments referring to the values in the parentheses. 4. INSIDE a class definition, one uses exactly the SAME NOTATION as one uses OUTSIDE a class definition. For example, inside the Blah class, one constructs a Blah object by: b = Blah(...) Just remember that SELF refers to an instance of the class being defined. 5. If an instance of the class needs to REMEMBER (store) information in order for you to implement a method, add YOUR OWN instance variable for that purpose to __init__. Choose a meaningful name for it, then use that instance variable as needed in other methods. Authors: David Mutchler, Vibha Alangar, Dave Fisher, Matt Boutell, Mark Hays, Mohammed Noureddine, Sana Ebrahimi, Sriram Mohan, their colleagues and PUT_YOUR_NAME_HERE. """ # TODO: 1. PUT YOUR NAME IN THE ABOVE LINE. import math import m1t_test_Line as m1t ############################################################################### # IMPORTANT: # Your instructor will lead you through this exercise. ############################################################################### # ----------------------------------------------------------------------------- # TODO: 2. Right-click on the src folder and # Mark Directory as ... Sources Root, # if you have not already done so. # ----------------------------------------------------------------------------- ############################################################################### # The Point class (and its methods) begins here. ############################################################################### class Point: """ Represents a point in 2-dimensional space. """ def __init__(self, x, y): """ Sets instance variables x and y to the given coordinates. """ # TODO: 3. What is SELF here? # Implement the above spec, using instance variables named x and y. def __repr__(self): """ Returns a fancy string representation of this Point. """ # TODO: 4. Read the above spec. (Ignore the details of the code below.) # What function that you use a LOT calls the __repr__ method? 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. """ # TODO: 5. Read the above spec. What would go wrong if we did NOT # define __eq__ for a Point. That is, why would we WANT to define EQ? return (round(self.x, 6) == round(p2.x, 6) and round(self.y, 6) == round(p2.y, 6)) def clone(self): """ Returns a new Point at the same (x, y) as this Point. """ # TODO: 6. What is SELF here? Implement the above spec. # Concepts illustrated: # -- constructing an instance of a class and # -- accessing instance variables # Note: SAME NOTATION as when NOT inside a class definition! def distance_from(self, p2): """ Returns the distance this Point is from the given Point. """ # TODO: 7. What is SELF here? What is p2 here? Read the code below. # Which feels better: the multi-line solution or the one-line solution? # Note (again): SAME NOTATION as when NOT inside a class definition! dx_squared = (self.x - p2.x) ** 2 dy_squared = (self.y - p2.y) ** 2 return math.sqrt(dx_squared + dy_squared) # Alternative solution: return ((self.x - p2.x) ** 2 + (self.y - p2.y) ** 2) ** 0.5 def halfway_to(self, p2): """ Returns a new Point half-way between this Point and given Point. """ # TODO: 8. What are SELF and p2 here? Implement the above spec. # Geometry hint: Average the x-coordinates and average the # y-coordinates to get the coordinates of the halfway-between Point. # (No subtraction! Although there is a formula for halfway that # uses subtraction, the formula for the average is simpler!) def plus(self, p2): """ Returns a Point whose coordinates are those of this Point PLUS the given Point. For example: p1 = Point(500, 20) p2 = Point(100, 13) p3 = p1.plus(p2) print(p3) would print: Point(600, 33) """ # TODO: 9. What are SELF and p2 here? Implement the above spec. def minus(self, p2): """ Returns a Point whose coordinates are those of this Point MINUS the given Point. For example: p1 = Point(500, 20) p2 = Point(100, 13) p3 = p1.minus(p2) print(p3) would print: Point(400, 7) """ # TODO: 10. What are SELF and p2 here? Implement the above spec. def main(): """ Calls the TEST functions in this module, but ONLY if the method to be tested has at least a partial implementation. That is, a TEST function will not be called until you begin work on the code that it is testing. """ if m1t.is_implemented("__init__"): run_test_init() if m1t.is_implemented("clone"): run_test_clone() if m1t.is_implemented("reverse"): run_test_reverse() if m1t.is_implemented("slope"): run_test_slope() if m1t.is_implemented("length"): run_test_length() if m1t.is_implemented("get_number_of_clones"): run_test_get_number_of_clones() if m1t.is_implemented("line_plus"): run_test_line_plus() if m1t.is_implemented("line_minus"): run_test_line_minus() if m1t.is_implemented("midpoint"): run_test_midpoint() if m1t.is_implemented("is_parallel"): run_test_is_parallel() if m1t.is_implemented("reset"): run_test_reset() ############################################################################### # The Line class (and its methods) begins here. ############################################################################### class Line(object): """ Represents a line segment in 2-dimensional space. """ def __init__(self, start, end): """ What comes in: -- self -- a Point object named start -- a Point object named end where the two Points are to be the initial start and end points, respectively, of this Line. Side effects: MUTATEs this Line by setting instance variables named: -- start -- end to CLONES of the two Point arguments, respectively. Type hints: :type start: Point :type end: Point """ # TODO: 11. What is SELF? What TYPE of objects are start and end? # What does putting the "Type hints" accomplish? # Implement the above spec. Use the Point class clone method! # Why is it wise to CLONE start and end? (See to_clone_or_not.pdf) def __repr__(self): """ Returns a string representation of this Line. """ # TODO: 12. Read the above spec. # What function that you use a LOT calls the __repr__ method? start = repr(self.start).replace("Point", "") end = repr(self.end).replace("Point", "") return "Line[{}, {}]".format(start, end) def __eq__(self, line2): """ Defines == for Lines: a == b is equivalent to a.__eq__(b). """ # TODO: 13. Read the above spec. Why would we WANT to define == ? # Then mark this _TODO_ as DONE. return (self.start == line2.start) and (self.end == line2.end) def clone(self): """ What comes in: -- self What goes out: Returns a new Line whose START is a clone of this Line's START and whose END is a clone of this Line's END. """ # TODO: 14. What is SELF? Implement the above spec. # Use the Point class clone method! def reverse(self): """ What comes in: -- self Side effects: MUTATES this Line so that its direction is reversed (that is, its start and end points are swapped). ** Must NOT mutate its start and end points -- just SWAP them. ** """ # TODO: 15. What is SELF? Implement the above spec. # What is the SWAP pattern? (Hint: temp variable, 3 lines, think Z.) def slope(self): """ What comes in: -- self What goes out: Returns the slope of this Line, or math.inf if the line is vertical (i.e., has "infinite" slope). """ # TODO: 16. What is SELF? Implement the above spec. # Geometry hint: slope = dy / dx. Guard against division by zero! def length(self): """ What comes in: -- self What goes out: Returns the length of this Line. """ # TODO: 17. What is SELF? Implement the above spec. # Geometry hint: length of a line is the distance from its start # point to its end point. Use the Point class distance_from method! # def get_number_of_clones(self): """ What comes in: -- self What goes out: -- Returns the number of times that this Line has been cloned (via the clone method). """ # TODO: 18. Do you see that you MUST introduce an instance variable # to STORE with the Line the number of times clone has been called? # Implement the above spec. # (Hint: add a new instance variable to __init__ and ...) def line_plus(self, other_line): """ What comes in: -- self -- another Line object What goes out: -- Returns a Line whose: -- start is the sum of this Line's start (a Point) and the other_line's start (another Point). -- end is the sum of this Line's end (a Point) and the other_line's end (another Point). Type hints: :type other_line: Line """ # TODO: 19. What is SELF? other_line? Implement this method. # Use methods from the Point class! def line_minus(self, other_line): """ What comes in: -- self -- another Line object What goes out: -- Returns a Line whose: -- start is this Line's start (a Point) minus the other_line's start (another Point). -- end is this Line's end (a Point) minus the other_line's end (another Point). Type hints: :type other_line: Line """ # TODO: 20. What is SELF? other_line? Implement this method. # Use methods from the Point class! def midpoint(self): """ What comes in: -- self What goes out: returns a Point at the midpoint of this Line. """ # TODO: 21. What is SELF? Implement this method. # Use a method from the Point class! def is_parallel(self, line2): """ What comes in: -- self -- another Line object (line2) What goes out: Returns True if this Line is parallel to the given Line (line2). Returns False otherwise. *** SEE THE IMPORTANT NOTE BELOW, re ROUNDING numbers. Type hints: :type line2: Line """ # TODO: 22. What is SELF? line2? Implement this method. # IMPORTANT NOTE: # Use the slope method, but round to 12 decimal places. def reset(self): """ What comes in: -- self Side effects: MUTATES this Line so that its start and end points revert to what they were when this Line was constructed. """ # TODO: 23. Do you see that you MUST introduce an instance variable # to STORE the original start and end Point objects # and that you CANNOT use SELF for that purpose? # Implement the above spec. # (Hint: add a new instance variable to __init__ and ...) ############################################################################### # The TEST functions for the Line class begin here. # # We have already written the TEST functions. They all take the form: # -- m1t.run_test_BLAH() # This runs OUR tests. # -- One more test (or set of tests) that came directly from the Example # in the specification. ############################################################################### def run_test_init(): """ Tests the __init__ method of the Line class. """ m1t.run_test_init() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(30, 17) p2 = Point(50, 80) line = Line(p1, p2) # Causes __init__ to run print(line.start) # Should print Point(30, 17) print(line.end) # Should print Point(50, 80) print(line.start == p1) # Should print True print(line.start is p1) # Should print False print("The above should print:") print(" Point(30, 17)") print(" Point(50, 80)") print(" True") print(" False") def run_test_clone(): """ Tests the clone method of the Line class. """ m1t.run_test_clone() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(30, 17) p2 = Point(50, 80) line1 = Line(p1, p2) line2 = line1.clone() print(line1) # Should print: Line[(30, 17), (50, 80)] print(line2) # Should print: Line[(30, 17), (50, 80)] print(line1 == line2) # Should print: True print(line1 is line2) # Should print: False print(line1.start is line2.start) # Should print: False print(line1.end is line2.end) # Should print: False line1.start = Point(11, 12) print(line1) # Should print: Line[(11, 12), (50, 80)] print(line2) # Should print: Line[(30, 17), (50, 80)] print(line1 == line2) # Should now print: False print("The above should print:") print(" Line[(30, 17), (50, 80)]") print(" Line[(30, 17), (50, 80)]") print(" True") print(" False") print(" False") print(" False") print(" Line[(11, 12), (50, 80)]") print(" Line[(30, 17), (50, 80)") print(" False") def run_test_reverse(): """ Tests the reverse method of the Line class. """ m1t.run_test_reverse() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(30, 17) p2 = Point(50, 80) line1 = Line(p1, p2) line2 = line1.clone() print(line1) # Should print: Line[(30, 17), (50, 80)] line1.reverse() print(line1) # Should print: Line[(50, 80), (30, 17)] print(line1 == line2) # Should print: False line1.reverse() print(line1 == line2) # Should now print: True print("The above should print:") print(" Line[(30, 17), (50, 80)]") print(" Line[(50, 80), (30, 17)") print(" False") print(" True") def run_test_slope(): """ Tests the slope method of the Line class. """ m1t.run_test_slope() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(30, 3) p2 = Point(50, 8) line1 = Line(p1, p2) # Since the slope is (8 - 3) / (50 - 30) , which is 0.25: print(line1.slope()) # Should print [approximately]: 0.25 line2 = Line(Point(10, 10), Point(10, 5)) print(line2.slope()) # Should print: inf # math.inf is NOT the STRING "inf", so: print(line2.slope() == "inf") # Should print False print("The above should print:") print(" 0.25 (approximately)") print(" inf") print(" False") def run_test_length(): """ Tests the length method of the Line class. """ m1t.run_test_length() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(166, 10) p2 = Point(100, 10) line1 = Line(p1, p2) # Since the distance from p1 to p2 is 66: print(line1.length()) # Should print: 66.0 p3 = Point(0, 0) p4 = Point(3, 4) line2 = Line(p3, p4) print(line2.length()) # Should print about 5.0 print("The above should print:") print(" 66.0") print(" 5.0 (approximately)") def run_test_get_number_of_clones(): """ Tests the get_number_of_clones method of the Line class. """ m1t.run_test_get_number_of_clones() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- line1 = Line(Point(500, 20), Point(100, 8)) line2 = line1.clone() line3 = line1.clone() line4 = line3.clone() line5 = line1.clone() print(line1.get_number_of_clones()) print(line2.get_number_of_clones()) print(line3.get_number_of_clones()) print(line4.get_number_of_clones()) print(line5.get_number_of_clones()) print("The above should print 3, then 0, then 1, then 0, then 0.") def run_test_line_plus(): """ Tests the line_plus method of the Line class. """ m1t.run_test_line_plus() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- line1 = Line(Point(500, 20), Point(100, 8)) line2 = Line(Point(100, 13), Point(400, 8)) line3 = line1.line_plus(line2) print(line3) print("The above should print: Line[(600, 33), (500, 16)]") def run_test_line_minus(): """ Tests the line_minus method of the Line class. """ m1t.run_test_line_minus() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- line1 = Line(Point(500, 20), Point(100, 8)) line2 = Line(Point(100, 13), Point(400, 8)) line3 = line1.line_minus(line2) print(line3) print("The above should print: Line[(400, 7), (-300, 0)]") def run_test_midpoint(): """ Tests the midpoint method of the Line class. """ m1t.run_test_midpoint() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(3, 10) p2 = Point(9, 20) line1 = Line(p1, p2) print(line1.midpoint()) # Should print: Point(6, 15) print("The above should print: Point(6, 15)") def run_test_is_parallel(): """ Tests the is_parallel method of the Line class. """ m1t.run_test_is_parallel() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- line1 = Line(Point(15, 30), Point(17, 50)) # slope is 10.0 line2 = Line(Point(10, 10), Point(15, 60)) # slope is 10.0 line3 = Line(Point(10, 10), Point(80, 80)) # slope is 7.0 line4 = Line(Point(10, 10), Point(10, 20)) # slope is inf print(line1.is_parallel(line2)) # Should print: True print(line2.is_parallel(line1)) # Should print: True print(line1.is_parallel(line3)) # Should print: False print(line1.is_parallel(line4)) # Should print: False print(line1.is_parallel(line1)) # Should print: True print(line4.is_parallel(line4)) # Should print: True print("The above should print:") print(" True, True, False, False, True, True") def run_test_reset(): """ Tests the reset method of the Line class. """ m1t.run_test_reset() # This runs OUR tests. # ------------------------------------------------------------------------- # One ADDITIONAL test (or set of tests). # ------------------------------------------------------------------------- p1 = Point(-3, -4) p2 = Point(3, 4) line1 = Line(p1, p2) line2 = Line(Point(0, 1), Point(10, 20)) line1.start = Point(100, 300) line2.end = Point(99, 4) line1.reverse() # Should print: Line[(x1, y1), (x2, y2)] where (x1, y1) and # (x2, y2) are the CURRENT coordinates of line1's endpoints. print(line1) print(line2) # Similarly for line2 line1.reset() line2.reset() print(line1) # Should print: Line[(-3, -4), (3, 4)] print(line2) # Should print: Line[(0, 1), (10, 20)] print("The above should print:") print(" Line[(3, 4), (100, 300)]") print(" Line[(0, 1), (99, 4)]") print(" Line[(-3, -4), (3, 4)]") print(" Line[(0, 1), (10, 20)]") # ----------------------------------------------------------------------------- # If this module is running at the top level (as opposed to being # imported by another module), then call the "main" function. # It is necessary here to enable the automatic testing in m1t_test_Line.py. # ----------------------------------------------------------------------------- if __name__ == "__main__": main()