A document from MCS 275 Spring 2023, instructor David Dumas. You can also get the notebook file.

MCS 275 Spring 2023 Homework 3 Solutions

  • Course Instructor: David Dumas
  • Contributors to this document: Kylash Viswanathan

Instructions:

  • Complete the problems below, which ask you to write Python scripts.
  • Upload your python code directly to gradescope, i.e. upload the .py files containing your work.

Deadline

This homework assignment must be submitted in Gradescope by Noon central time on Tuesday 31 January 2023.

Collaboration

Collaboration is prohibited, and you may only access resources (books, online, etc.) listed below.

Content

This assignment is about object-oriented programming, based on worksheets 1-3 and lectures 1-4. It focuses on the material of worksheet 3.

Resources you may consult

The course materials you may refer to for this homework are:

Point distribution

This homework assignment has 2 problems, numbered 2 and 3. The grading breakdown is:

Points Item
3 Autograder
6 Problem 2
6 Problem 3
15 Total

The part marked "autograder" reflects points assigned to your submission based on some simple automated checks for Python syntax, etc. The result of these checks is shown immediately after you submit.

What to do if you're stuck

Ask your instructor or TA a question by email, in office hours, or on discord.

Problem 1 doesn't exist

In Gradescope, the score assigned to your homework submission by the autograder (checking for syntax and docstrings) will be recorded as "Problem 1". Therefore, the numbering of the actual problems begins with 2.

This will happen on every assignment this semester. Starting on the next homework, I won't comment on this practice in the homework, and will just omit problem 1.

Problem 2: The universal mimetic class

Put the work you do for this problem in a file hwk3prob2.py.

Make a Python class UniversalMimic whose constructor takes no arguments (e.g. you can make one with UniversalMimic()). Use operator overloading to make it so that any instance of this class thinks it is equal to every other object. For example:

In [3]:
U = UniversalMimic()
In [4]:
1 == U
Out[4]:
True
In [5]:
U == 0
Out[5]:
True
In [6]:
U == None
Out[6]:
True
In [7]:
U == "a slice of apple pie"
Out[7]:
True
In [8]:
U == 2.75
Out[8]:
True
In [9]:
U == { "course": "MCS 275", "meeting time": "12:00pm", "enrollment_max": 28 }
Out[9]:
True

Note that in an expression like A == UniversalMimic(), it's possible that A might have its own __eq__ method that returns False, and since Python checks with the left object first, you can't avoid this test returning False. Don't worry about that. Just make your best effort to ensure all equality comparisons this class controls will return True.

Remark: This is a contrived test of overloading, and not something you should do in actual code.

Solution

In [2]:
class UniversalMimic:
    "The UniversalMimic class identifies as being equal to all other objects"

    # Note that no constructor is required as all objects inherit the no-argument constructor from the object class
    
    def __eq__(self, other):
        "Returns true for all objects"
        return True

Problem 3: Into the third dimension

Put the work you do for this problem in a file hwk3prob3.py.

In lecture 4, we built a module plane.py that represents

  • Two-dimensional points $(x,y)$ using the Point2 class, and
  • Two-dimensional vectors $\langle x,y \rangle$ using the Vector2 class

Take that module and modify it (renaming it to hwk3prob3.py) so that it instead contains these two classes:

  • Point3 representing three-dimensional points $(x,y,z)$ and
  • Vector3 representing three-dimensional vectors $\langle x,y,z \rangle$.

Mostly this will involve small changes to account for the third coordinate/component.

But also add one new method to Vector3:

  • def cross(self,other): : Returns the vector cross product $(\mathrm{self}) \times (\mathrm{other})$. Thus if v and w are Vector3 objects, you can compute the cross product (which is another Vector3) as v.cross(w).

Also take care to update __abs__ so it knows the right way to compute length in 3 dimensions.

3D geometry

No prerequisite course of MCS 275 has 3-dimensional geometry in its required topic list, so here are the things you need to know in this problem:

  • Adding 3-dimensional vectors to each other and to points is just like the 2-dimensional case: It happens separately for each component, but now there are $x$, $y$, and $z$ components.
  • The vector cross product is given by this formula:
$$ \langle a,b,c \rangle \times \langle d,e,f \rangle = \langle bf - ce, \, cd-af, \, ae-bd\rangle $$
  • The length of vector $\langle a,b,c \rangle$ is given by the formula
$$\sqrt{a^2 + b^2 + c^2}.$$

If anything about the geometric side of this question is unclear, please ask! We mean for it to be a question about translating those ideas into code.

Solution

In [ ]:
class Point3:
    "3D Point representation in the plane"

    def __init__(self, x, y, z):
        "Initialize new point from x, y, and z coordinates"
        self.x = x
        self.y = y
        self.z = z

    def __eq__(self, other):
        "points are equal if and only if they have same coordinates"
        if isinstance(other, Point3):
            return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)
        else:
            return False

    def __add__(self, other):
        "point3 + vector3 addition"
        if isinstance(other, Vector3):
            #add Point3 to another Vector3
            return Point3(self.x + other.x, self.y + other.y, self.z + other.z)
        else:
            # dont allow Point3 addition to other objects
            return NotImplemented  

    def __sub__(self, other):
        "point-point subtraction, gives displacement vector"
        if isinstance(other, Point3):
            return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)
        else:
            return NotImplemented

    def __str__(self):
        "human-readable string representation"
        return "Point3({},{},{})".format(self.x, self.y, self.z)

    def __repr__(self):
        "unambiguous string representation"
        return str(self)

    def distance_to(self, other):
        "get distance between two points"
        if isinstance(other, Point3):
            return abs(self - other)
        else:
            raise TypeError("A Point3 object is needed to compute the distance")


class Vector3:
    "Displacement 3D vector in the plane"

    def __init__(self, x, y, z):
        "Initialize new vector from x, y, and z components"
        self.x = x
        self.y = y
        self.z = z

    def __eq__(self, other):
        "vectors are equal if and only if they have same components"
        if isinstance(other, Vector3):
            return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)
        else:
            return False

    def __add__(self, other):
        "vector addition"
        if isinstance(other, Vector3):
            # vector+vector = vector
            return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)
        elif isinstance(other, Point3):
            # vector + point = point
            return Point3(self.x + other.x, self.y + other.y, self.z + other.z)
        else:
            # vector + anything else = nonsense
            return NotImplemented  # return this to forbid the requested operation

    def __mul__(self, other):
        "vector-scalar multiplication"
        if isinstance(other, (float, int)):  # isinstance allows a tuple of types
            # vector*scalar is vector
            return Vector3(self.x * other, self.y * other, self.z * other)
        else:
            return NotImplemented

    def __rmul__(self, other):
        "scalar-vector multiplication"
        # Called if other*self already attempted but failed
        # for example if other is an int or float and self is a Vector3
        # This "second chance" reflected version of multiplication lets the
        # right hand operand decide what to do.  In this case, we just decide
        # that other*self is the same as self*other (handled by Vector3.__mul__ above)
        return self * other

    def __neg__(self):
        "unary minus"
        return Vector3(-self.x, -self.y, -self.z)

    def __pos__(self):
        "unary plus: return a copy of the object"
        return self

    def __abs__(self):
        "abs means length of a vector"
        return (self.x * self.x + self.y * self.y + self.z * self.z) ** 0.5  

    def __str__(self):
        "human-readable string representation"
        return "Vector3({},{},{})".format(self.x, self.y, self.z)

    def __repr__(self):
        "unambiguous string representation"
        return str(self)

    def cross(self, other):
        "computes and returns the cross product of two 3D vectors"
        x_cross_comp = self.y * other.z - self.z * other.y
        y_cross_comp = self.z * other.x - self.x * other.z
        z_cross_comp = self.x * other.y - self.y * other.x
        return Vector3(x_cross_comp, y_cross_comp, z_cross_comp)

Revision history

  • 2023-01-26 Initial publication