# The simplest from-scratch approach is to represent vectors as lists of numbers. 
# A list of three numbers corresponds to a vector in three-dimensional space, 
# and vice versa:

height_weight_age = [70,  # inches,
                     170, # pounds,
                     40 ] # years

grades = [95,     # exam1
          80,     # exam2
          75,     # exam3
          62 ]    # exam4

# One problem with this approach is that we will want to perform arithmetic on vectors.
# Because Python lists aren’t vectors (and hence provide no facilities for 
# vector arithmetic), we’ll need to build these arithmetic tools ourselves.

# Vector addition:

def vector_add(v, w):
    """adds corresponding elements"""
    return [v_i + w_i for v_i, w_i in zip(v, w)]

# Vector subtraction:

def vector_subtract(v, w):
    """subtracts corresponding elements"""
    return [v_i - w_i
            for v_i, w_i in zip(v, w)]

x = [1,2,3]
y = [2,1,4]
z = vector_add(x,y)
print(x,y,z)

# ##########################################################
# Adding MULTIPLE vectors:
#
# Example:
#
#    vector_sum( [[1,2,3], [1,1,1], [2,2,2]] ) = [4,5,6]

def vector_sum(vectors):
    """sums all corresponding elements"""

    result = vectors[0]				# result = the first vector
    for vector in vectors[1:]: 			# loop over the others vectors
        result = vector_add(result, vector) 	# and add next vector to result
    return result

print( vector_sum( [[1,2,3], [1,1,1], [2,2,2]] ) )

# ################################################################################
# Another way to do vector_sum():
#
#    If you think about the algorithm, we are just reduce-ing 
#    the list of vectors using vector_add !!!
#    Which means we can rewrite this more briefly using higher-order functions:
# ################################################################################

from functools import reduce

def vector_sum(vectors):
    return reduce(vector_add, vectors)

print( vector_sum( [[1,2,3], [1,1,1], [2,2,2]] ) )


# ***********************************************************
# Scalar-vector multiplication function

def scalar_multiply(c, v):
    """c is a number, v is a vector"""
    return [c * v_i for v_i in v]

# ************************************************************************
# vector_mean(vectors)
#
#   computes the componentwise means of a list of (same-sized) vectors:
#
#

def vector_mean(vectors):
    """compute the vector whose ith element is the mean of 
       all the ith elements of the input vectors"""

    n = len(vectors)
    return scalar_multiply(1/n, vector_sum(vectors))


# ************************************************************************
# vector dot product:
#
# Interpretation of the dot product:
#
#   The dot product measures how far the vector v extends in the w direction. 
#
# For example, if w = [1, 0] then dot(v, w) is just the first component of v. 
# Another way of saying this is that it’s the length of the vector you’d get 
# if you projected v onto w (Figure 4-2).

def dot(v, w):
    """v_1 * w_1 + ... + v_n * w_n"""

    return sum(v_i * w_i for v_i, w_i in zip(v, w))

# Related to dot-product:

def sum_of_squares(v):
    """v_1 * v_1 + ... + v_n * v_n"""
    return dot(v, v)

# And now we can compute the Euclidian norm of a vector:

import math

def magnitude(v):
    return math.sqrt(sum_of_squares(v)) 	# math.sqrt is square root function


# And now we can compute the DISTANCE between 2 points v and w (vectors)

def squared_distance(v, w):
    """(v_1 - w_1) ** 2 + ... + (v_n - w_n) ** 2"""

    return sum_of_squares(vector_subtract(v, w))

def distance(v, w):
    """This is the Euclidean distance between 2 vectors"""

    return math.sqrt(squared_distance(v, w))



