Because the code for this and the next chapter build on each other, I’m approaching the way I talk about these two sets of exercises a bit differently than previous posts. First, let’s take a look at the instructions.
Instructions
[1] Add a
distanceFromPoint
method that works similar todistanceFromOrigin
except that it takes aPoint
as a parameter and computes the distance between that point and self.
[2] Add a method
reflect_x
to Point which returns a new Point, one which is the reflection of the point about the x-axis. For example,Point(3, 5).reflect_x()
is (3, -5)
[3] Add a method
slope_from_origin
which returns the slope of the line joining the origin to the point. For example,What cases will cause your method to fail? Return None when it happens.
[4] The equation of a straight line is “y = ax + b”, (or perhaps “y = mx + c”). The coefficients a and b completely describe the line. Write a method in the Point class so that if a point instance is given another point, it will compute the equation of the straight line joining the two points. It must return the two coefficients as a tuple of two values. For example,
This tells us that the equation of the line joining the two points is “y = 2x + 3”. When will your method fail?
[5] Add a method called
move
that will take two parameters, call themdx
anddy
. The method will cause the point to move in the x and y direction the number of units given. (Hint: you will change the values of the state of the point)
[6] Given three points that fall on the circumference of a circle, find the center and radius of the circle.
Starting Point
As you can probably tell from the first question, we had been building on the Point class throughout the entire chapter. Here is was the code I was working with by the time I got to the exercises:
Class Point:
""" Point class for representing and manipulating x,y coordinates. """
def __init__(self, initX, initY):
""" Create a new point at the given coordinates. """
self.x = initX
self.y = initY
def getX(self):
return self.x
def getY(self):
return self.y
def distanceFromOrigin(self):
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
def __str__(self):
return "x=" + str(self.x) + ", y=" + str(self.y)
A quick note for anyone who isn’t familiar with Object Oriented Programming: in this and the next post, you are going to see a lot of references to the keyword self
. self
, similar to this
in PHP and JavaScript, is simply a reference to the object that is currently in use. An object is kind of like a variable in that it can be manipulated. However, unlike a variable, Objects are complex bits of information that have defined characteristics and actions (aka methods, which is just a class’ version of functions) that can be referenced and used.
An example of this the Turtle object that I have used quite abundantly in this Thinkcspy series. Turtles have characteristics like position, color, and shape as well and actions like goto
, left
, and penup
. In this and the next chapter, I learned to create something like Turtles.
Discussion of the Solutions
I’m not going to discuss every solution because some of them are fairly self-explanatory. I will however discuss the more interesting and complex problems.
Problems 3 and 4: Points of Failure
For both of these problems, I was asked to consider where my solutions would fail. Because division is involved in both solutions, the obvious point of failure would be division by 0, which would indicate that the slope is either vertical or nonexistent if the two points happen to point to the same location. For some reason, this was not immediately apparent to me for the problem 3. I’m embarrassed to admit that as of time of writing, I just added a check for that in my solution to that problem.
Problem 6: Circumcenter
Mkay…. Do y’all remember having to calculate the radius and center of a circle in high school geometry class? ‘Cause I don’t. Shot out to Khan Academy (and AI) for helping me understand this concept.
The thought process behind finding a circle from three points is that in certain angles, three points can be connected to form a triangle. For any given triangle, there is a single circle that encapsulates said triangles. From there you can surmise that in order to find the center of the circle, you need find the intersection of the lines that run through the midpoints of each side of the triangle. Once the center has been calculated, you can use it to find the distance between itself and one of three points, which just so happens to be the radius.
Phew… math class over. Now how do we do this programmatically? Well first, we need a method to calculate the midpoint. With that, we can find the midpoint of two of the lines and then use those two lines to find the intersection, which also happens to be a midpoint. Now why just two lines instead of three? Well, if we know where the intersection of two lines are, then we know the intersection of all three lines. Adding a third line in the equation would be redundant.
For your convenience, here is the primary method used for this problem:
def findCenterRadius(self, pnt1, pnt2):
m1, b1 = self.slopeIntercept(pnt1)
m2, b2 = self.slopeIntercept(pnt2)
if m1 == m2 and b1 == b2:
return "Error: Points are on the same line."
mid1X, mid1Y = self.getMid(pnt1)
mid2X, mid2Y = self.getMid(pnt2)
center = Point(mid1X, mid1Y).getMid(Point(mid2X, mid2Y))
r = self.distanceFromPoint(Point(center[0], center[1]))
return "Circumcenter: {}; Radius: {}".format(center, r)
In observing this block of code, you’ve probably noticed that I’m not actually using lines to find the midpoints. I’m using points. Even where a line is referenced with calls to slopeIntercept()
, that doesn’t figure into the calculation of the center. Let me explain (cue your favorite sappy R&B/pop song here).
The calls to slopeIntercept()
are there to make sure all three points don’t sit on the same line. self
is used as the central point to test this. This is because if the points form a triangle, then self
acts as an apex. If they don’t, self
is a simply another point on a straight line. The way I test for this in the program is by comparing the slope and intercepts. If both are the same, that means they represent the same line. In that case, there would be no point in attempting to find a center or radius.
And that’s it! Check out the code below to see the solutions to the other problems as well as the methods that were called in this problem.
The Code
# Runestone.Academy thinkcspy course
# Chapter 17
# Problems 1 - 6
import math
class Point:
""" Point class for representing and manipulating x,y coordinates. """
def __init__(self, initX, initY):
""" Create a new point at the given coordinates. """
self.x = initX
self.y = initY
def getX(self):
return self.x
def getY(self):
return self.y
def distanceFromOrigin(self):
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
# Solution to Problem 1
def distanceFromPoint(self, pnt):
dx = self.x - pnt.getX()
dy = self.y - pnt.getY()
return math.sqrt(dx**2 + dy**2)
# Solution to Problem 2
def reflect_x(self):
return (self.x, -self.y)
# Solution to Problem 3
def slopeFromOrigin(self):
if self.x == 0:
return None
return self.y / self.x
# Solution to Problem 4
def slopeIntercept(self, pnt):
dx = self.x - pnt.getX()
dy = self.y - pnt.getY()
# Accounting for points of failure (divide by 0)
if dx == 0:
if dy == 0:
return "Error: Cannot calculate slope of a single point."
else:
return "Error: Cannot calculate slope of a vertical line."
m = dy / dx
b = self.y - m*self.x
return (m, b)
# Helper function for Problem 6
def getMid(self, pnt):
midX = (self.x + pnt.getX()) / 2
midY = (self.y + pnt.getY()) / 2
return (midX, midY)
# Solution to Problem 6
def findCenterRadius(self, pnt1, pnt2):
m1, b1 = self.slopeIntercept(pnt1)
m2, b2 = self.slopeIntercept(pnt2)
if m1 == m2 and b1 == b2:
return "Error: Points are on the same line."
mid1X, mid1Y = self.getMid(pnt1)
mid2X, mid2Y = self.getMid(pnt2)
center = Point(mid1X, mid1Y).getMid(Point(mid2X, mid2Y))
r = self.distanceFromPoint(Point(center[0], center[1]))
return "Circumcenter: {}; Radius: {}".format(center, r)
# Solution to Problem 5
def move(self, pnt):
self.x += pnt.getX()
self.y += pnt.getY()
return (self.x, self.y)
def __str__(self):
return "x=" + str(self.x) + ", y=" + str(self.y)