Monday, 30 April 2018

Memoization :

Memoization is a technique for remembering the results that incur huge costs to running time to the algorithm.
Memoization in python is generally carried out using dictionary(hash) data type.
Memoization technique is simpler than it sounds.

A brief idea using example:
Let's take example of factorial function : 

'Native way : '
 
def fact(n):
    if n in [0, 1] : return 1
    else : return n*fact(n-1)

This is what we use generally for computing factorial.
Let's compute factorial for first 5000 natural numbers and calculate time:

'Native way : '
from time import time
def fact(n):
    if n in [0, 1] : return 1
    else : return n*fact(n-1)
start_time = time()
for i in range(5000):
    fact(i)
end_time = time()
print("Time required :", end_time-start_time)

Output :
Time required : 12.179325103759766

This computation took 12 secs in my OS!!
This may be lesser for you, but it is still a big time.
This is because, at every integer n, it  recursed for n-1 times.

Memoization does a smart move here.
Memoization says that, when you know that the result that you got will be used later on, why dont you store it??

Hence, in program, we will store value of computed value at each step.

memory = {}  # a dictionary storing n : fact(n)
def fact(n):
    try : return memory[n]
    except:
        "compute"
        if n in [0, 1] : return 1
        else :
            memory[n] = n*fact(n-1)
            return memory[n]

In this function, we have used try except:
try checks if key n is present in our memory dictionary.
If it is not present, it is computed, stored and returned.

Let's test it for its time complexity :
'Memoized '
from time import time
memory = {}  # a dictionary storing n : fact(n)
def fact(n):
    try : return memory[n]
    except:
        "compute"
        if n in [0, 1] : return 1
        else :
            memory[n] = n*fact(n-1)
            return memory[n]
start_time = time()
for i in range(5000):
    fact(i)
end_time = time()
print("Time required :", end_time-start_time)

OUTPUT:
Time required : 0.014145374298095703

see the fraction of difference!!
Memoized version is almost 860 times better than naive factorial function.

More Native functions memoized:

1) Fibonacci series:
memory = {}
def fib(n):
    try : return memory[n]
    except:
        if n <= 1 : return n
        else : return fib(n-1)+fib(n-2)

List Comprehension : 

 List comprehension is used to create an iterable which follows a particular fashion.

List comprehensions are a syntactic sugar for creating a list(iterable).

>>>"Expected output : [0, 0, 0, 0, 0]"
>>> 'Native way : '
>>> list = []
>>> for i in range(5):
...     list.append(0)
...
>>> list
[0, 0, 0, 0, 0]

"Using list comprehension : "
>>> list = [0 for i in range(5)]
>>> list
[0, 0, 0, 0, 0]
Isn't it fun and easy to read and write??

Let's understand it.

list = [0 for i in range(5)]
Here, there is a loop counter i which takes value from 0, 4 using the range function and foreverey value of the loop counter, 
0 is appended to the list.

Another example:
Creating [0, 1, 2, 3, 4, 5] : 
In previous example, we had appended 0 for each value of loop counter, 
Now, we will append the loop counter value:

>>> list = [i for i in range(5)]
>>> list
[0, 1, 2, 3, 4]
 Note : this can be achieved using list(range(5)), but for example i did this in that way.


just like normal for loop, we can iterate over an iterable, same can be done in list comprehension.
For example : 
1)
a = [1, 4, 7, 2]
b = [i+1 for i in a]

>>> a = [1, 4, 7, 2]
>>> b = [i+1 for i in a]
>>> b
[2, 5, 8, 3]

2)
 >>> names = ['math', 'pyqt', 'numpy', 'matplotlib', 'keras', 'tensorflow']
>>> length = [len(name) for name in names]
>>> length
[4, 4, 5, 10, 5, 10]

3)
>>> numbers = [2, 5, 1, 9]
>>> squares = [n*n for n in numbers]
>>> squares
[4, 25, 1, 81]

Easy, correct??

You may have a doubt that, There are some conditions where the list elements are not following same output which is dependent on conditions.
List comprehension supports if else (any boolean) conditions.

Case 1) Only if condition is needed.
Structure:
[operation for something in something  if condition]
>>> numbers = [3, 2, 1, 4, 5, 7, 6]
>>> even = [n for n in numbers if n%2==0]
>>> even
[2, 4, 6]

Case 2) Both if and else condition is needed.
Structure:
[cto if cond else fco for iteratioon]
cond :  condition to be checked
cto : condition true operation
fco : condition false operation.

Example:
>>> numbers = [3, 2, 1, 4, 5, 7, 6]
>>> even = [1 if n%2 == 0 else 0 for n in numbers]
>>> even
[0, 1, 0, 1, 0, 0, 1]



Nested List comprehension : 

It is generally used in representing graphs and multi-dimensional datatypes.
for example:

1)
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
 can be created using:
 >>> [[0 for i in range(4)] for i in range(4)]
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

2)
0 1 2 3
0 1 2 3
0 1 2 3
0 1 2 3
>>> [[i for i in range(4)] for i in range(4)]
[[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]

3)
Problem : Create a 2d list whose (i ,j)th element is (i+j)

Soln:

0 1 2 3 
1 2 3 4
2 3 4 5
3 4 5 6
>>> [[i+j for i in range(4)] for j in range(4)]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]

4)
In Engineering / related courses, we generally come across situations for initializing with some conditions like:
(i, j)th element   =    0            if i = j;
                                 (i+j)!     otherwise

Soln:
0   1  2     6
1   0  6     24
2   6  0     120
6  24 120 0
>>> [[factorial(i+j) if i !=j else 0 for i in range(4)] for j in range(4)]
[[0, 1, 2, 6], [1, 0, 6, 24], [2, 6, 0, 120], [6, 24, 120, 0]]
Note : List comprehensions are not only used to create a list. ;)

A generator object, set, dictionary ....... any iterable,
except for tuples.

>>> (i for i in range(5))
<generator object <genexpr> at 0x7f654145a4c0>

This is what happens when you try to create a tuple.
Although, type casting does the task.


Sets : 

>>> {2*i for i in range(7)}
{0, 2, 4, 6, 8, 10, 12}

Dictionary:

>>> cubes = {i : i*i*i  for i in range(7)}
>>> cubes
{0: 0, 1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216}

Splat Operator:

'*' is the splat operator in python
spalt operator is also known as scatter operator.
Splat operator is used to unpack a packed data type like list.

Generally, splat operator is used in methods (function) which can have any number of elements as input.

For example:
average method:

>>> def average(*elements):
...     s = 0
...     for e in elements:
...         s += e
...     print("Average :", s/len(elements))
...
>>> average(1,2,3)
Average : 2.0
>>> average(1,2,3,6,8,0)
Average : 3.3333333333333335


Note : Spalts are also used in retrieving elements from zipped objects.

For example:
>>> zip_object = zip(['one', 'two', 'three'], [1,2,3])
>>> zip_object
<zip object at 0x7f654145ce88>
>>> unzipped_list = [*zip_object]
>>> unzipped_list
[('one', 1), ('two', 2), ('three', 3)]

In Python3,
splat operator has more use in unpacking and assigning.

>>> a, *b, c = range(10)
>>> print(a, b, c)
0 [1, 2, 3, 4, 5, 6, 7, 8] 9

This property of unpacking is useful for handling multiple number of return parameters where all of them may/not be used.

Also in some revisions, user is also unaware of number of return parameters. In that case, splat is very useful.

>>> def unpredictable(n):
...     '''returns any number of random nuumbers upto n'''
...     return [random(1, n) for i in range(random(1, n))]
...
>>> a, b, *d = unpredictable(10)
>>> a
1
>>> b
6
>>> d
[3, 3, 5, 4, 7, 9]


Saturday, 3 February 2018

iv)List :
      List is the best of the data types in python3.
      It can hold anything(Literally anything).
      Syntax :
             There are two ways you can declare an empty list:
                          list() or []

            >>> type(list())
    <class 'list'>
    >>> type([])
    <class 'list'>
>>> [1,2,"string", """ multiline \n comment """, 12.564]
[1, 2, 'string', ' multiline \n comment ', 12.564]

>>> a = [2, 5, 45.74, "python"]
>>> a[0]
2
>>> a[3]
'Python'
Python has a special provision for accessing elements from last in case the length of list is unknown.
You can access the last element by using negative indexing.
List[-n] returns nth element counting reverse , starting from the last in the List  .
>>> a[-1]
'Python'
>>> a[-3]
5
Checking length(number of elements) of list :
    len(list) is the method used to find.
        >>> len(a)
4
 
Slicing in list :
    Slicing is a process of selection of elements by using indexes.
    List[a:b] will return list of elements in List from index a to b
        Excluding element at bth position. b>=a
    If a/b parameters are not provided then python takes extreme
        Position.
        For example :
>>> a[2:4]
[45.74, 'python']
>>> a[2:1]
[]
>>> a[3:]
['python']
>>> a[:3]
[2, 5, 45.74]
>>> a[:]
[2, 5, 45.74, 'python']
Slicing with negative index :
>>> a[:-1]
[2, 5, 45.74]
>>> a[:-2]
[2, 5]
a[-x ] is same as a[len(a) - x ]
>>> a[:-1]
[2, 5, 45.74]
>>> a[:-2]
[2, 5]

Reversing a list :
>>> a[::1]
[2, 5, 45.74, 'python']
    reversed(x) can be used to reverse a list.
    Rerversed function returns a reverseiterator . So, it has
        to be type casted to list.
>>> list (reversed(a))
['python', 45.74, 5, 2]

Counting occurrences of certain element in a list.
Syntax : list.count(element_to_find_frequency_of)
Example :
>>> [1, 1, 2, 1, 2, 5, 8 ,6, ""].count(1)
3
Sorting a list :
    sort attribute can be used to sort a list.
    To use sort attribute, list cannot have any other type other than
        that of int and float.
    sort attribute does not return anything but performs sorting on
        the list.
>>> a = [1, 2, 5, 4, 3]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5]

Pop attribute of list :
Syntax : list.pop(index)
             Where, index is index of element you want to remove
             from the list.
This attribute returns the deleted element.
    [1, 3.23, 4, 5]
>>> a.pop(1)
3.23
>>> a
[1, 4, 5]
Copying a list :
    Suppose a=[1,2,3] is a list.
    b = a will give reference of a to b, not copying the list.
    Now, what does that mean??
    That means, when you will modify elements of a, elements of
        b will also be affected and vice versa.

    For copying the elements of a list to another, copy attribute is
        Used.
    b = a.copy()
    Now, elements in b will have elements same as a but
       modifying a or b won’t affect the other list.

Append attribute :
Append takes exactly one argument as an input that will
include the argument passed inside the attribute in the list.
>>> a
[1, 45, 3, 4, 5]
>>> a.append([1,2,3])
>>> a
[1, 45, 3, 4, 5, [1, 2, 3]]

Here, i append a list [1, 2, 3] to the list a.

Removing all the elements :
clear attribute is used.
        >>> a.clear()
>>> a
[]

List can be merged with another list using extend attribute of the list
list.extend(list1)
>>> a = [1, 2, 3, 5]
>>> a.extend([4, 6])
>>> a
[1, 2, 3, 5, 4, 6]
This is one of the way you can add a list to another list.
Another way is :
list = list + list1
>>> a = a + [9, 7, 8]
>>> a
[1, 2, 3, 5, 4, 6, 9, 7, 8]
These were some of the  attributes and functionalities you can do using list. 
More properties we will see in future posts.

Before starting with our next data Type, Let’s first know about variables in python:
Variables :
    These are any names which can be assigned any data type:
Python has no restrictions on use of variables, We don’t need
any explicit declaration of type of variable. Python assigns
data type of the variable by the data it holds.
Confused, correct?? Let’s take an example:
    >>> a = 3
Is a variable declaration where, a is  variable name and 3 is an
int data type assigned to it.
>>> type(a)
<class 'int'>
You can overwrite same  variable to hold value of same or different data type.
    >>> a = 32
>>> a = 45
>>> a
45
>>> a = "string"
>>> a
'string'
>>> type(a)
<class 'str'>

Using variable to access the data it is storing.
>>> a = 23
>>> b = 3
>>> a+b
26
>>> a**b
12167
One of the best things what python provides is simultaneous
assignment of variables.
For example :
>>> a,b = 12,"string"
>>> a
12
>>> b
'string'

>>> a, b, c = 2,1,    9
>>> a+b+c
12

Memoization : Memoization is a technique for remembering the results that incur huge costs to running time to the algorithm. Memoizati...