intro-to-python/sample_package/vector.py

142 lines
4.2 KiB
Python
Raw Normal View History

"""This module defines a Vector class."""
from .matrix import Matrix
class Vector:
"""A standard one-dimensional vector from linear algebra.
The class is designed for sub-classing in such a way that
the user can adapt the typing class attribute to change,
for example, how the entries are stored (e.g., as integers).
Attributes:
storage (callable): must return an iterable that is used
to store the entries of the vector; defaults to tuple
typing (callable): type casting applied to all vector
entries upon creation; defaults to float
zero_threshold (float): maximum difference allowed when
comparing an entry to zero; defaults to 1e-12
"""
storage = tuple
typing = float
zero_threshold = 1e-12
def __init__(self, data):
"""Initiate a new vector.
Args:
data (iterable): the vector's entries;
must have at least one element
Raises:
ValueError: if the provided data do not have enough entries
"""
self._entries = self.storage(self.typing(x) for x in data)
if len(self) == 0:
raise ValueError("the vector must have at least one entry")
def __repr__(self):
name, args = self.__class__.__name__, ", ".join(f"{x:.3f}" for x in self)
return f"{name}(({args}))"
def __str__(self):
name, first, last, entries = (
self.__class__.__name__,
self[0],
self[-1],
len(self),
)
return f"{name}({first:.1f}, ..., {last:.1f})[{entries:d}]"
def __len__(self):
return len(self._entries)
def __getitem__(self, index):
if not isinstance(index, int):
raise TypeError("index must be an integer")
return self._entries[index]
def __iter__(self):
return iter(self._entries)
def __reversed__(self):
return reversed(self._entries)
def __add__(self, other):
if isinstance(other, self.__class__):
if len(self) != len(other):
raise ValueError("vectors need to be of the same length")
return self.__class__(x + y for (x, y) in zip(self, other))
elif isinstance(other, numbers.Number):
return self.__class__(x + other for x in self)
return NotImplemented
def __radd__(self, other):
return self + other
def __sub__(self, other):
return self + (-other)
def __rsub__(self, other):
return (-self) + other
def __mul__(self, other):
if isinstance(other, self.__class__):
if len(self) != len(other):
raise ValueError("vectors need to be of the same length")
return sum(x * y for (x, y) in zip(self, other))
elif isinstance(other, numbers.Number):
return self.__class__(x * other for x in self)
return NotImplemented
def __rmul__(self, other):
return self * other
def __truediv__(self, other):
if isinstance(other, numbers.Number):
return self * (1 / other)
return NotImplemented
def __eq__(self, other):
if isinstance(other, self.__class__):
if len(self) != len(other):
raise ValueError("vectors need to be of the same length")
for x, y in zip(self, other):
if abs(x - y) > self.zero_threshold:
return False
return True
return NotImplemented
def __pos__(self):
return self
def __neg__(self):
return self.__class__(-x for x in self)
def __abs__(self):
return norm(self)
def __bool__(self):
return bool(abs(self))
def __float__(self):
if len(self) != 1:
raise RuntimeError("vector must have exactly one entry to become a scalar")
return self[0]
def as_matrix(self, *, column=True):
"""Convert the vector into a matrix.
Args:
column (bool): if the vector should be interpreted as
as a column vector or not; defaults to True
Returns:
matrix (Matrix)
"""
if column:
return Matrix([x] for x in self)
return Matrix([(x for x in self)])