import numpy as np

np.random.random()   #Random real number uniformly distributed between 0 and 1

np.random.normal()  #Random real number following Gaussian distribution with mean=0 and standard deviation=1

"""
Explanation: Lists, numpy arrays and all those confusing essential things that we do all the time
Package importing
End of explanation
"""


from numpy import random
random.normal()

#OR:
from numpy.random import normal
normal()

"""
Explanation: Or you can also import a particular function or a subpackage from a package like this
End of explanation
"""


mylist=[1,2,3,'a',True,[5,6]]

print mylist
print mylist[5]

"""
Explanation: Lists in python
Lists are some of the most useful data structures in basic python. You can put anything you want in a list, you can change individual cells, elements can be lists themselves too.
End of explanation
"""


print range(5)
print range(4,10)

"""
Explanation: An extremely useful pre-defined function is "range":
End of explanation
"""


listA=range(10)
listB=[2*elem for elem in listA]
print listA
print listB

listC=[(idx,2**elem) for idx,elem in enumerate(listA)]
listD=[2**elem for idx,elem in enumerate(listA) if elem>4]
print listC
print listD

listE=[('id:'+str(idx),'power:'+str(2**elem)) for idx,elem in enumerate(listA)]
print listE

"""
Explanation: One of the most useful things about lists in python, is that they can be defined recursively, in a very "math" way
End of explanation
"""


print np.arange(10)
print np.arange(5,10)

print np.arange(1.5,4,.5)  #start, end (excluded) and step
print np.arange(3,4,.1)

print 1/np.arange(0,10.5,.5)

"""
Explanation: Numpy array and indexing
You can create a numpy array a couple of different ways. One of them is to use np.arange, the equivalent of range() we saw for lists, but also creates floats
The BEST thing about numpy arrays, is that you can do math on them treating them as single numbers, and it will do the operations to EACH element by default!
End of explanation
"""


print np.array([1,2,3])

print np.array([3**x for x in np.arange(0,4,.5)])    

#THIS CAN BE DONE BETTER WITH THIS:
myarray=3**np.arange(0,4,.5)
print myarray

print myarray.shape
print myarray.shape[0]

"""
Explanation: OR we can produce lists and then convert them to numpy arrays in a straightforward way:
End of explanation
"""


print myarray[3:5]  #3 to 5, 5 excluded
print myarray[3:]   #3 onwards
print myarray[:4]    #Until 4, i.e. 0,1,2,3
print myarray[:-2]   #Until two from the end, i.e. exclude the last two

indexing_list=[3,4,5]
print myarray[indexing_list]

print np.ones(4)

boolean_indexing=np.ones_like(myarray)
print boolean_indexing

boolean_indexing[[4,5,6]]=False
print boolean_indexing.astype('bool')

print np.arange(myarray.shape[0])[boolean_indexing.astype('bool')]

print np.ones((3,4))

y,x=np.mgrid[:4,:5]
print x

print y

print x**2+y**2

"""
Explanation: Indexing is pretty flexible
End of explanation
"""


