How to not print "Decimal" in my output for vectors? - python

from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector(object):
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot normalize the zero vector'
def __init__(self, coordinates):
try:
if not coordinates:
raise ValueError
self.coordinates = tuple([Decimal(x) for x in coordinates])
self.dimension = len(self.coordinates)
except ValueError:
raise ValueError('The coordinates must be nonempty')
except TypeError:
raise TypeError('The coordinates must be an iterable')
def __str__(self):
return 'Vector: {}'.format(self.coordinates)
def __eq__(self, v):
return self.coordinates == v.coordinates
def plus(self,v):
new_coordinates = [x+y for x,y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def minus(self,v):
new_coordinates = [x-y for x,y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def times_scalar(self,c):
new_coordinates = [c*x for x in self.coordinates]
return Vector(new_coordinates)
def magnitude(self):
coordinates_squared = [x**2 for x in self.coordinates]
return Decimal(sqrt(sum(coordinates_squared)))
def normalized(self):
try:
magnitude = self.magnitude()
return self.times_scalar(Decimal(1.0)/magnitude)
except ZeroDivisionError:
raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)
#normalization
v3 = Vector([5.581, -2.136])
v4 = Vector([1.996, 3.108, -4.554])
print(v3.normalized())
print(v4.normalized())
The output is:
Vector: (Decimal('0.933935214086640295130539147343'), Decimal('-0.357442325262329983594964055642'))
Vector: (Decimal('0.340401295943301353537171045562'), Decimal('0.530043701298487295255023200306'), Decimal('-0.776647044952802835008995686630'))
Why is this? How can I get it to print without the word "Decimal"?

Printing a tuple defaults to displaying the __repr__() of the contained objects, which is a technical representation for debugging and usually includes the type. What you want is the __str__() (the "pretty" representation) so you must format them yourself.
Also note that you must initialize Decimal with strings or you will capture the floating point inaccuracy you are trying to avoid.
Example (removed unnecessary code not necessary to demonstrate the issue):
from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector:
def __init__(self, coordinates):
self.coordinates = tuple([Decimal(x) for x in coordinates])
def __str__(self):
return f"Vector: ({', '.join(str(x) for x in self.coordinates)})"
v1 = Vector([5.581, -2.136]) # Initialized without strings
v2 = Vector([1.996, 3.108, -4.554])
v3 = Vector(['5.581', '-2.136'])
v4 = Vector(['1.996', '3.108', '-4.554'])
print(v1)
print(v2)
print(v3)
print(v4)
Output:
Vector: (5.58100000000000040500935938325710594654083251953125, -2.13600000000000012079226507921703159809112548828125)
Vector: (1.995999999999999996447286321199499070644378662109375, 3.108000000000000095923269327613525092601776123046875, -4.5540000000000002700062395888380706310272216796875)
Vector: (5.581, -2.136)
Vector: (1.996, 3.108, -4.554)

If you want to get a string with all the decimals, use the builtin str method:
>>> str(decimal.Decimal(2.1))
'2.100000000000000088817841970012523233890533447265625'
If you want a specific number of decimals use Python string formatting:
>>> f"{decimal.Decimal(2.1):0.2}" # Use :0.n where n is the number of decimals
'2.1'
or with .format
>>> "{:0.2}".format(decimal.Decimal(2.1))
'2.1'
Reference
EDIT
Your __str__ method would be something like this:
def __str__(self):
return 'Vector: {}'.format(str(x) for x in self.coordinates)

Related

Typeerror: unsupported operand type(s) for /: 'Decimal' and 'float'

Here is my code: I have tried all day to solve the mistake but I still have the error:
TypeError: unsupported operand type(s) for /: 'Decimal' and 'float'.
from math import sqrt, acos, pi
from decimal import Decimal, getcontext
getcontext().prec = 30
class Vector(object):
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot compute an angle with the zero vector'
def __init__(self, coordinates):
try:
if not coordinates:
raise ValueError
self.coordinates = tuple(Decimal(x) for x in coordinates)
self.dimension = len(self.coordinates)
except ValueError:
raise ValueError('The coordinates must be nonempty')
except TypeError:
raise TypeError('The coordinates must be an iterable')
#orthogonal or parallel
def is_zero(self, tolerance=1e-10):
return self.magnitude() < tolerance
def is_orthogonal_to(self, v, tolerance=1e-10):
return abs(self.dot(v)) < tolerance
def is_parallel_to(self, v):
return ( self.is_zero() or
v.is_zero() or
self.angle_with(v) == 0 or
self.angle_with(v) == pi )
#dot product &angle
def dot(self, v):
return sum([x*y for x,y in zip(self.coordinates,v.coordinates)])
def angle_with(self,v,in_degrees=False):
try:
u1 = self.normalized()
u2 = v.normalized()
angle_in_radians = acos(u1.dot(u2))
if in_degrees:
degrees_per_radian = 180./pi
return angle_in_radians * degrees_per_radian
else:
return angle_in_radians
except Exception as e:
if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:
raise Exception('Cannot compute an angle with the zero vector')
else:
raise e
#magnitude& Direction
def magnitude(self):
coordinates_squared = [x**2 for x in self.coordinates]
return sqrt(sum(coordinates_squared))
def normalized(self):
try:
magnitude = self.magnitude()
return self.times_scalar(Decimal('1.0')/magnitude)
except ZeroDivisionError:
raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)
#Plus,minus, scalar multiply
def plus(self, v):
new_coordinates = [x+y for x,y in zip(self.coordinates,v.coordinates)]
return Vector(new_coordinates)
def minus(self, v):
new_coordinates = [x-y for x,y in zip(self.coordinates,v.coordinates)]
return Vector(new_coordinates)
def times_scalar(self, c):
new_coordinates = [Decimal(c)*x for x in self.coordinates]
return Vector(new_coordinates)
def __str__(self):
return 'Vector: {}'.format(self.coordinates)
def __eq__(self, v):
return self.coordinates == v.coordinates
v = Vector(['8','-9'])
w = Vector(['-1','-1'])
print v.dot(w)
print v.angle_with(w)
print v.angle_with(w, in_degrees=True)
In general it usually does not make sense to mix Decimal and float math. Not sure why you need Decimal here, but with two small changes this code can be made to run. I would suggest considering getting rid of the Decimal unless you are sure you need it:
Change magnitude to:
# magnitude& Direction
def magnitude(self):
coordinates_squared = [x ** 2 for x in self.coordinates]
return sum(coordinates_squared) ** Decimal('0.5')
And the acos call to:
angle_in_radians = Decimal(str(acos(u1.dot(u2))))

How to check if one list is equal to another list created using a class?

from math import pi
class Circle(object):
'Circle(x,y,r)'
def __init__(self, x=0, y=0, r=1):
self._r = r
self._x = x
self._y = y
def __repr__(self):
return 'Circle({},{},{})'.\
format(self.getx(), self.gety(),\
self.getr())
#silly, but has a point: str can be different from repr
def __str__(self):
return 'hello world'
def __contains__(self, item):
'point in circle'
px, py = item
return (self.getx() - px)**2 + \
(self.gety() - py)**2 < self.getr()**2
def getr(self):
'radius'
return self._r
def getx(self):
'x'
self._lst.append(self._x)
return self._x
def gety(self):
'y'
self._lst.append(self._y)
return self._y
def setr(self,r):
'set r'
self._r = r
def setx(self,x):
'set x'
self._x = x
def sety(self,y):
'set y'
self._y = y
def move(self,x,y):
self._x += x
self._y += y
def concentric(self, d):
d = self._list
def area(self):
'area of circle'
return (self.getr())**2*pi
def circumference(self):
'circumference of circle'
return 2*self.getr()*pi
My question is worded kinda awkwardly but what I am trying to do is check if 2 different circles have the same center (x,y). I think the easiest way to solve this would be to input the 2 points into a list but I am not sure how to compare the 2 lists as every time i try my code it adds everything to the same list
Add the following method to your Circle class.
def equal_center(self, other):
'check if another circle has same center'
return (self._x == other._x) & (self._y == other._y)
Usage
C1 = Circle(3, 5, 8)
C2 = Circle(3, 5, 10)
C3 = Circle(3, 2, 1)
C1.equal_center(C2) # True
C1.equal_center(C3) # False
I would recommend creating a function which takes two circle objects and returns if the coordinates are the same or not by comparing the x and y values of each object:
def same_center(circle_1, circle_2):
if circle_1.getx() == circle_2.getx() and circle_1.gety() == circle_2.gety():
return True
else:
return False
This solution is much easier than using lists and should be easy to implement.
If you have two instances of the class...
a = Circle(0,0,1)
b = Circle(0,0,1)
You could add them to a list of circles...
circles = [a,b]
And loop through the list, checking their values...
for i in circles:
for j in filter(lambda x : x != i, circles):
if i._x == j._x and i._y == j._y:
return True #two circles have same center
This should work for n instances of the class, though if its only two you want to check
if a._x == b._x and a._y == a._y:
return True

Prevent numpy from vectorizing multiplication

I created a set of little helper classes for working with degrees and radians. I have included some numpy ufuncs (radians, degrees, sin, cos...) so that I can have deg objects inside of numpy arrays, and perform numpy trig operations (e.g., np.cos(np.array([5*deg, 10*deg, 15*deg]))) on the numpy arrays.
However, I have discovered that when "multiplying" an ndarray by the deg class on the RHS, the numpy object's __mul__ method gets invoked on the array, rather than UnitMeta.__rmul__ leading to raising a TypeError inside of NumberMixin.__new__ as desired. It works correctly (raises an error) when deg is the LHS, e.g. deg * np.array([1]).
Only a portion of the classes is shown below for brevity.
'deg.py'
from numbers import Number
import numpy as np
class UnitMeta(type):
def __mul__(cls, other):
return cls(other)
def __rmul__(cls, other):
'''So can write things like "1 * deg"'''
return cls(other)
class NumberMixin():
def __new__(cls, v):
if not isinstance(v, Number):
raise TypeError('A valid numeric type value is required. {} is not numeric.'.format(type(v)))
return super().__new__()
def __mul__(self, other):
return self._v * other
def __rmul__(self, other):
return self.__mul__(other)
def radians(self): # NOTE: overridden in deg below
return np.radians(self._v)
def degrees(self): # NOTE: overridden in deg below
return np.degrees(self._v)
def sin(self):
return np.sin(self._v)
def cos(self):
return np.cos(self._v)
def tan(self):
return np.tan(self._v)
class deg(NumberMixin, metaclass = UnitMeta):
def __init__(self, d = 0.0):
if isinstance(d, deg):
self._v = d._v
self._deg = d._deg
else:
self._v = np.radians(d)
self._deg = d
def __mul__(self, other):
if isinstance(type(other),UnitMeta):
return NotImplemented
else:
return super().__mul__(other)
def __str__(self):
return str(self._deg) + '°'
def __repr__(self):
return str('deg({})'.format(self._deg))
def __format__(self, spec):
return self._deg.__format__(spec) + '°'
def degrees(self):
return self
def radians(self):
return self._v
if __name__ == '__main__':
try:
print('FAILURE: ', deg * np.array([1]) , ' exception not caught')
except TypeError:
print('SUCCESS: deg * np.array([1]) exception caught.')
try:
print('FAILURE: ', np.array([1]) * deg, ' exception not caught')
except TypeError:
print('SUCCESS: np.array([1]) * deg exception caught.')
Everything else works well.
I want to prevent numpy from doing this so that I can write things more like "normal math", e.g.:
5 * deg
...but have an exception come up, and not get swallowed, when something like this accidentally occurs:
a = np.array([5])
a * deg <- TypeError
Any suggestions? I haven't worked with numpy much as of yet, so apologies if there is an obvious solution.

Law of cosines returns 90 degrees for all angles in a triangle?

I'm working on an assignment where I make a class called Triangle that takes in three sides and returns information about the triangle. Specifically, I am trying to calculate the three angles within the triangle, but for some reason I am getting 90 degrees for all angles, which is impossible. I have gone over my code tons of times but can't find the mistake and would greatly appreciate some help. Thank you.
from math import acos, degrees, sqrt
import turtle
class Triangle:
def __init__(self, side_a=None, side_b=None, side_c=None):
self.side_a = side_a
self.side_b = side_b
self.side_c = side_c
def is_triangle(self):
return ((self.side_a + self.side_b) > self.side_c) \
and ((self.side_a + self.side_c) > self.side_b) \
and ((self.side_b +self.side_c) > self.side_a)
def perimeter(self):
if self.is_triangle():
return self.side_a + self.side_b + self.side_c
else:
return None
def area(self):
if self.is_triangle():
s = self.perimeter()/2
print s
return sqrt((s*(s-self.side_a)*(s-self.side_b)*(s-self.side_c)))
else:
return None
def a_angle(self):
if self.is_triangle():
return degrees(acos((self.side_b**2 + self.side_c**2 - self.side_a**2)/(2*self.side_b*self.side_c)))
else:
return None
def b_angle(self):
if self.is_triangle():
return degrees(acos((self.side_c**2 + self.side_a**2 - self.side_b**2)/(2*self.side_c*self.side_a)))
else:
return None
def c_angle(self):
if self.is_triangle():
return degrees(acos((self.side_a**2 + self.side_b**2 - self.side_c**2)/(2*self.side_a*self.side_b)))
else:
return None
def angle(self):
return self.a_angle(), self.b_angle(), self.c_angle()
def __str__(self):
pass
tri = Triangle(11, 7, 9)
print tri.is_triangle()
print tri.perimeter()
print tri.area()
print tri.a_angle()
print tri.b_angle()
print tri.c_angle()
print tri.angle()
In Python 2, division of integers by default truncates the result to an integer, so something like 5/2 will equal 2. In your case, dividing the sides results in zero.
The simplest way to fix it is to do from __future__ import division at the very beginning of your file. This makes division always return a float.

Why does the print statement at the bottom of my main method not print anything?

I'm working on the MIT open courseware for CS-600 and I can't figure out why the last print statement isn't printing anything. Here's the code I wrote:
#!/usr/bin/env python
# encoding: utf-8
# 6.00 Problem Set 9
#
# Name:
# Collaborators:
# Time:
from string import *
class Shape(object):
def area(self):
raise AttributeException("Subclasses should override this method.")
class Square(Shape):
def __init__(self, h):
"""
h: length of side of the square
"""
self.side = float(h)
def area(self):
"""
Returns area of the square
"""
return self.side**2
def __str__(self):
return 'Square with side ' + str(self.side)
def __eq__(self, other):
"""
Two squares are equal if they have the same dimension.
other: object to check for equality
"""
return type(other) == Square and self.side == other.side
class Circle(Shape):
def __init__(self, radius):
"""
radius: radius of the circle
"""
self.radius = float(radius)
def area(self):
"""
Returns approximate area of the circle
"""
return 3.14159*(self.radius**2)
def __str__(self):
return 'Circle with radius ' + str(self.radius)
def __eq__(self, other):
"""
Two circles are equal if they have the same radius.
other: object to check for equality
"""
return type(other) == Circle and self.radius == other.radius
#
# Problem 1: Create the Triangle class
#
## TO DO: Implement the `Triangle` class, which also extends `Shape`.
class Triangle(Shape):
def __init__(self, base, height):
self.base = float(base)
self.height = float(height)
def area(self):
return self.base*self.height/2
def __str__(self):
return 'Triangle with base ' + str(self.base) + 'and height ' + str(self.height)
def __eq__(self, other):
return type(other) == Triangle and self.base == other.base and self.height == other.height
#
# Problem 2: Create the ShapeSet class
#
## TO DO: Fill in the following code skeleton according to the
## specifications.
class ShapeSet(object):
def __init__(self):
"""
Initialize any needed variables
"""
self.allCircles = []
self.allSquares = []
self.allTriangles = []
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
self.place = None
def addShape(self, sh):
"""
Add shape sh to the set; no two shapes in the set may be
identical
sh: shape to be added
"""
if not isinstance(sh, Shape): raise TypeError('not a shape')
if isinstance(sh, Square):
for sq in self.allSquares:
if sh == sq:
raise ValueError('shape already in the set')
self.allSquares.append(sh)
if isinstance(sh, Triangle):
for tri in self.allTriangles:
if sh == tri:
raise ValueError('shape already in the set')
self.allTriangles.append(sh)
if isinstance(sh, Circle):
for circ in self.allCircles:
if sh == circ:
raise ValueError('shape already in the set')
self.allCircles.append(sh)
def __iter__(self):
"""
Return an iterator that allows you to iterate over the set of
shapes, one shape at a time
"""
self.place = 0
return self
def next(self):
if self.place >= len(self.allShapes):
raise StopIteration
self.place += 1
return self.allShapes[self.place - 1]
def __str__(self):
"""
Return the string representation for a set, which consists of
the string representation of each shape, categorized by type
(circles, then squares, then triangles)
"""
shapeList = ""
for item in self.allShapes:
shapeList += item.get__str__ + "br/"
return shapeList
#
# Problem 3: Find the largest shapes in a ShapeSet
#
def findLargest(shapes):
"""
Returns a tuple containing the elements of ShapeSet with the
largest area.
shapes: ShapeSet
"""
## TO DO
#
# Problem 4: Read shapes from a file into a ShapeSet
#
def readShapesFromFile(filename):
"""
Retrieves shape information from the given file.
Creates and returns a ShapeSet with the shapes found.
filename: string
"""
## TO DO
def main():
sq1 = Square(4.0)
sq2 = Square(5.0)
sq3 = Square(3.0)
circ1 = Circle(3.0)
circ2 = Circle(3.2)
tri1 = Triangle(3.0, 4.0)
tri2 = Triangle(4.0, 3.0)
tri3 = Triangle(1.0, 1.0)
thisSet = ShapeSet()
thisSet.addShape(sq1)
thisSet.addShape(sq2)
thisSet.addShape(sq3)
thisSet.addShape(circ1)
thisSet.addShape(circ2)
thisSet.addShape(tri1)
thisSet.addShape(tri2)
thisSet.addShape(tri3)
print thisSet
if __name__ == '__main__':
main()
This line:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
doesn't do what you think it does. It sets allShapes to an empty list, and then as you add shapes later, nothing updates allShapes.
Then your __str__ function just loops over allShapes, which is still empty, so your __str__ returns an empty string.
This line makes allShapes an empty list:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
If you modify allCircles, that doesn't affect allShapes. I would personally eliminate allShapes, and in the str method, add them at the last possible second:
for item in self.allCircles + self.allSquares + self.allTriangles:
The problem is here:
self.allShapes = self.allCircles + self.allSquares + self.allTriangles
When you concatenate lists like this, the result is a copy of the component lists. So when those lists are changed later, the concatenated list isn't changed. In this case, self.allCircles, etc. are all empty. So self.allShapes is an empty list too; the for loop in ShapeSet.__str__ doesn't append anything to ShapeList, and so the result is an empty string.
One simple way to fix this would be to make allShapes a method that you call, and that returns a new concatenation of self.allCircles... etc. each time it's called. That way, allShapes is always up-to-date.
If this is your actual code, then it must be because of
item.get__str__
which should raise an exception.
Edit: as others have noted, this isn't the actual problem, but I leave this here as a hint for further progress. Mind you, it's considered bad style ("unpythonic") to call x.__str__() directly, as you probably intended. Call str(x) instead, even in the implementation of __str__.
You assign allShapes to be the value of self.allCircles + self.allSquares + self.allTriangles at the start in your init method (when the other lists are empty).
It's value is then never changed, so it remains empty.
You need this in addShape:
self.allShapes.append(sh)

Categories