Example of performing Vector mathmatical function using Python List structures


Vector methods to be created:

  • Sum vectors
    • Add vector elements of same sized vectors
    • Return resulting vector
  • Subtract vectors
    • Subtract vector elements of same sized vectors
    • Return resulting vector
  • Product of vectors
    • Product of components of vectors
    • Return resulting vector
  • Product of vector and scalar
    • Return scalar product of each element of vector
  • Mean of vectors
    • Sum Vector method / number of elements for each element (or 1/len scalar multiply)
  • Dot Product
    • Sum of component wise products
    • Multiply vectors
    • Sum vectors
    • Return resulting vector

Teaching notes delete when finished

Remember to explain that in the real world numpy and other libraries would be used to do this

  • For teaching list methods
    • Particuliarly allows for a number of list comprehensions to be explained
  • Basic Class definition and issues
    • Start with just calling a definition directly (which will Error with a not found)
    • Show how adding self.function_name() works and explain
    • Move into using decorators
  • Start with a vector with a small number of elements
    • So students can do calculations in their heads and follow along

In [35]:
class vector_math:
    '''
    This is the base class for vector math - which allows for initialization with two vectors.
    '''
    
    def __init__(self, vectors = [[1,2,2],[3,4,3]]):
        self.vect1 = vectors[0]
        self.vect2 = vectors[1]
        
    def set_vects(self, vectors):
        self.vect1 = vectors[0]
        self.vect2 = vectors[1]
    
    def sum_vects(self):
        return [x + y for x, y in zip(self.vect1, self.vect2)]
    
    def sub_vects(self):
        # default should be [-2,-2,-1]
        return [x - y for x, y in zip(self.vect1, self.vect2)]
        # Can expand out to for x, y in zip: ... to show what it and sum do
    
    def multi_vects(self):
        #default should be [3,8,6]
        return [x * y for x, y in zip(self.vect1, self.vect2)]
    
    def multi_scalar(self, scalar, vect):
        return [e * scalar for e in vect]
        # Show difference between just element * number and using tuple from zip()
        
    def multi_scalar_l(self, scalar, vect):
        return lambda e: e * scalar, vect
        
    def mean_vects(self):
        mean_vect = self.sum_vects()
        return self.multi_scalar(1/len(mean_vect), mean_vect)
        
    def dot_product(self):
        return sum(self.multi_vects())
    
vect = vector_math()

sum_vect = vect.sum_vects()
print("Sum of vectors = {}".format(sum_vect))

print("Subtraction of vectors = {}".format(vect.sub_vects()))
print("Product of vectors = {}".format(vect.multi_vects()))
print("Product of Sum of vectors and 2 = {}\n".format(vect.multi_scalar(2, sum_vect)))
# Yep can still use character returns and others in format

print("Average of vectors = {}".format(["{:.2f}".format(e) for e in vect.mean_vects()]))
# Now there are other ways to reduce the decimal places but this was just to show a nested format call

# TODO: Consider adding timeit to show difference between calling multi_scalar directly and calling mean_vect:
#print("Average of vectors through calling scalar = {}".format(
#        ["{:.2f}".format(e) for e in vect.multi_scalar(1/len(sum_vect), sum_vect)]))

print("The Dot Product is {}".format(vect.dot_product()))


Sum of vectors = [4, 6, 5]
Subtraction of vectors = [-2, -2, -1]
Product of vectors = [3, 8, 6]
Product of Sum of vectors and 2 = [8, 12, 10]

Average of vectors = ['1.33', '2.00', '1.67']
The Dot Product is 17

Other vector operations that could be done


In [32]:
from math import sqrt

# Using the vect variables showing without functions
sum_of_squares = sum([x * y for x, y in zip(vect.vect1, vect.vect1)])
magnitude = sqrt(sum_of_squares)
distance = sqrt(sum([(x - y) ** 2 for x, y in zip(vect.vect1, vect.vect2)]))

print("Sum of Squares is {}".format(sum_of_squares))
print("Magnitude is {:.2f}".format(magnitude))
print("Distance is {}".format(distance))


Sum of Squares is 9
Magnitude is 3.00
Distance is 3.0

List Comprehensions are Powerful tools in Python

Expect to see them throughout code one has to maintain but also understand they are not always the optimal solution

When an iteration is needed to build a composite value, list comprehensions are considered the most readable or understandable way to achieve this. Loops may be used instead if one wants the "side effect" of an interation while functional tools may be used if optimization and code speed is important.

For instance, the above examples could also have been performed with an annoymous lambda or reduce, like:

def multi_scalar(self, vect, scalar):
    return lambda e: e * scalar, vect

In this case, the lambda would be faster by a minimal amount and actually have one less function call - which are expensive in Python. This is not always true as the need for an increasing amount of functional methods can change both the speed and amount of function call required. code example is below


In [61]:
import dis
import time

# For instruction - shows disassemble of methods and performs quick time check

vect = [2,3,3,3,4,5,6,6,4,3,2,1,3,4,5,6,4,3,2,1,3,4,5,6,4,3,2]

t1 = time.time()

print("list comp")
dis.dis(compile("[e * 2 for e in vect]", '<stdin>', 'exec'))

d_l = time.time() - t1
print(d_l)

t2 = time.time()

print("\n\n\nlambda")
dis.dis(compile("lambda e: e * 2, vect", '<stdin>', 'exec'))

d_lam = time.time() - t2
print(d_lam)


list comp
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x7fb1987a35d0, file "<stdin>", line 1>)
              3 LOAD_CONST               1 ('<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_NAME                0 (vect)
             12 GET_ITER
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             16 POP_TOP
             17 LOAD_CONST               2 (None)
             20 RETURN_VALUE
0.00032782554626464844



lambda
  1           0 LOAD_CONST               0 (<code object <lambda> at 0x7fb19872c1e0, file "<stdin>", line 1>)
              3 LOAD_CONST               1 ('<lambda>')
              6 MAKE_FUNCTION            0
              9 LOAD_NAME                0 (vect)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               2 (None)
             19 RETURN_VALUE
0.00039315223693847656

In [ ]: