import numpy as np

"""
Explanation: <!-- <img src="files/images/python-screenshot.jpg" width="600"> -->
<img src="imgs/header.png">
Basics of Numerical Python Arrays (numpy)
1. In-place Arithmetics
End of explanation
"""


a = np.array(range(10000000))
b = np.array(range(9999999,-1,-1))

%%time
a = a + b 

"""
Explanation: Case 1: a = a+b
The sum is first computed and resulting in a new array and the a is bound to the new array
End of explanation
"""


a = np.array(range(10000000))
b = np.array(range(9999999,-1,-1))

%%time
a +=b 

"""
Explanation: Case 2: a += b
The elements of b are directly added into the elements of a (in memory) - no intermediate array. These operators implement the so-called "in-place arithmetics" (e.g., +=, *=, /=, -= )
End of explanation
"""


#Apply function to a complete array instead of writing loop to iterate over all elements of the array. 
#This is called vectorization. The opposite of vectorization (for loops) is known as the scalar implementation

def f(x):
    return x*np.exp(4)

print(f(a))

"""
Explanation: 2. Vectorization
End of explanation
"""


x = np.array(range(100))

x[1:-1] # picks out all elements except the first and the last, but contrary to lists, a[1:-1] is not a copy of the data in a.
x[0:-1:2] # picks out every two elements up to, but not including, the last element, while 
x[::4] # picks out every four elements in the whole array.

"""
Explanation: 3. Slicing and reshape
Array slicing
x[i:j:s]

picks out the elements starting with index i and stepping s indices at the time up to, but not including, j.
End of explanation
"""


a = np.linspace(-1, 1, 6)
print (a)

a.shape
a.size

# rows, columns
a.shape = (2, 3) 
a = a.reshape(2, 3) # alternative

a.shape
print (a)

# len(a) always returns the length of the first dimension of an array. -> no. of rows

"""
Explanation: Array shape manipulation
End of explanation
"""


Z = np.ones((10,10))
Z[1:-1,1:-1] = 0
print(Z)

"""
Explanation: Exercise
1. Create a 10x10 2d array with 1 on the border and 0 inside
End of explanation
"""


Z = np.zeros(10, [ ('position', [ ('x', float, 1),
                                   ('y', float, 1)]),
                    ('color',    [ ('r', float, 1),
                                   ('g', float, 1),
                                   ('b', float, 1)])])
print(Z)

"""
Explanation: 2. Create a structured array representing a position (x,y) and a color (r,g,b)
End of explanation
"""


x = np.random.rand(5e7)

%timeit np.power(x,3)
%timeit x*x*x

"""
Explanation: 3. Consider a large vector Z, compute Z to the power of 3 using 2 different methods
End of explanation
"""


