001 - Vectors & Vector Operations
Challenge
Implement vector addition, subtraction, dot product, and L1/L2 norms using only basic arrays (no linear algebra library calls). Verify each against a library (e.g., NumPy), since Python does not have built-in vector operations.
My Approach
Firstly, I chose how to define a vector in Python. For this first project, I wanted to keep it simple, as a list of numbers.
For all the binary vector operations (operations that need two inputs), I checked the sizes first. If they were the same size, I proceeded; if not, I raised a ValueError. This was done using this function:
def _check_sizes(a, b):
if len(a) != len(b):
raise ValueError("Both vectors must have the same size.")Vector Addition
Vector addition definition:
For vector addition, I just added every corresponding element (element-wise). I used a normal loop like this:
def vec_add(a, b):
_check_sizes(a, b)
res = []
for single_a, single_b in zip(a, b):
res.append(single_a + single_b)
return resNotice that, at the beginning, I used the private function _check_sizes() to ensure the operation was valid (it raises a ValueError if not).
Vector Subtraction
Vector subtraction definition:
For vector subtraction, the idea was still the same as vector addition, except every element was subtracted (as opposed to addition).
def vec_sub(a, b):
_check_sizes(a, b)
res = []
for single_a, single_b in zip(a, b):
res.append(single_a - single_b)
return resVector Dot product
Vector dot product definition:
For dot product, I multiplied the corresponding elements and summed them into one scalar. There was a caveat for this function: it assumed real number input. If the input was a complex number, I handled it like a normal real number case.
Why did I state this? Because there is a different dot product definition called the Hermitian Product, which states we take the conjugate of one vector before multiplying. Since I wanted this to be simple, I handled it as a normal real number case.
def vec_dot(a, b):
_check_sizes(a, b)
res = 0
for single_a, single_b in zip(a, b):
res += single_a * single_b
return resVector L1 and L2 Norm
For this, I used the general definition of the norm, which is:
There is something to keep in mind: the norm is defined for any real number . So I added a check to ensure before proceeding.
def vec_norm(a, order):
if not order >= 1:
raise ValueError("Order must be greater than or equal to 1.")
res = 0
for single_a in a:
res += abs(single_a) ** order
res = res ** (1 / order)
return resVerifying Against a Library
Since Python doesn't have a built-in vector library, I checked my implementation against the NumPy library on several cases. For testing, I used the pytest library.
For vector addition, I checked it against these cases:
- simple addition
- commutative check
- integer and float addition
- size doesn't match case
For the implementation, it looked like this:
import numpy as np
import pytest
from vector import vec_add
def test_vec_add():
a = [3, 4, 5]
b = [-4, 0, 3]
c = [1.5, 2.75, -1.2]
d = [3, -4]
assert vec_add(a, b) == np.add(np.array(a), np.array(b)).tolist() # simple addition check
assert vec_add(b, c) == np.add(np.array(c), np.array(b)).tolist() # commutative check
with pytest.raises(ValueError): # size doesn't match check
vec_add(a, d)Then I also did the same for all other functions implemented here. The testing implementation basically looked the same as the vector addition ones. Here is the list of what I checked:
For vector subtraction:
- simple subtraction
- integer and float subtraction
- size doesn't match
For vector dot product:
- simple dot product
- commutative check
- integer and float dot product
- size doesn't match check
For L1 and L2 norm:
- checking known pythagorean triples and quadruples
- float Lp norm check
- float order ValueError check
- less than 1 order ValueError check
Source Code
You can find the source code here:
In the source code you can find additional things like type hintings, docstrings, and file structure that I omit in this explanation.