# 4 Control and Loops

## 4.1 If Statement

### 4.1.1 Multiline If.. Statements

price = 102
if price <100:
elif price < 110:
print ('hold')
elif price < 120:
else:
print ('sell')
#:> hold
print('end of programming')
#:> end of programming

### 4.1.2 Single Line If .. Statement

#### 4.1.2.1 if … In One Statement

price = 70
if price<80: print('buy')
#:> buy

#### 4.1.2.2 Ternary Statemnt

This statement return a value with simple condition

price = 85
'buy' if (price<80) else 'dont buy'
#:> 'dont buy'

## 4.2 For Loops

### 4.2.1 For .. Else Construct

else is only executed when the for loop completed all cycles


mylist = [1,2,3,4,5]

for i in mylist:
print (i)
else:
print('Hooray, the loop is completed successfully')
#:> 1
#:> 2
#:> 3
#:> 4
#:> 5
#:> Hooray, the loop is completed successfully

In below exmaple, for loop encountered break, hence the else section is not executed.

for i in mylist:
if i < 4:
print (i)
else:
print('Oops, I am breaking out half way in the loop')
break
else:
print('Hooray, the loop is completed successfully')
#:> 1
#:> 2
#:> 3
#:> Oops, I am breaking out half way in the loop

### 4.2.2 Loop thorugh ‘range’

for i in range (1,10,2):
print ('Odds Number : ',i) 
#:> Odds Number :  1
#:> Odds Number :  3
#:> Odds Number :  5
#:> Odds Number :  7
#:> Odds Number :  9

### 4.2.3 Loop through ‘list’

#### 4.2.3.1 Standard For Loop

letters = ['a','b','c','d']
for e in letters:
print ('Letter : ',e)
#:> Letter :  a
#:> Letter :  b
#:> Letter :  c
#:> Letter :  d

#### 4.2.3.2 List Comprehension

Iterate through existing list, and build new list based on condition
new_list = [expression(i) for i in old_list]

s = ['abc','abcd','bcde','bcdee','cdefg']
[x.upper() for x in s]
#:> ['ABC', 'ABCD', 'BCDE', 'BCDEE', 'CDEFG']

Extend list comprehension can be extended with if condition**
new_list = [expression(i) for i in old_list if filter(i)]

old_list    = ['abc','abcd','bcde','bcdee','cdefg']
matching = [ x.upper() for x in old_list if 'bcd' in x ]
print( matching )
#:> ['ABCD', 'BCDE', 'BCDEE']

### 4.2.4 Loop Through ‘Dictionary’

Looping through dict will picup key

d = {"x": 1, "y": 2}
for key in d:
print (key, d[key])
#:> x 1
#:> y 2

## 4.3 Generators

• Generator is lazy, produce items only if asked for, hence more memory efficient
• Generator is function with ‘yield’ instead of ‘return’
• Generator contains one or more yields statement
• When called, it returns an object (iterator) but does not start execution immediately
• Methods like iter() and next() are implemented automatically. So we can iterate through the items using next()
• Once the function yields, the function is paused and the control is transferred to the caller
• Local variables and their states are remembered between successive calls
• Finally, when the function terminates, StopIteration is raised automatically on further calls

### 4.3.1 Basic Generator Function

Below example give clear understanding of how generator works

def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n

n += 1
print('This is printed second')
yield n

n += 1
print('This is printed at last')
yield n
a = my_gen()
type(a)
#:> <class 'generator'>
next(a)
#:> This is printed first
#:> 1
next(a)
#:> This is printed second
#:> 2

### 4.3.2 Useful Generator Fuction

Generator is only useful when it uses for-loop - for-loop within generator - for-loop to iterate through a generator

def rev_str(my_str):
length = len(my_str)
for i in range(length - 1,-1,-1):
yield my_str[i]
for c in rev_str("hello"):
print(c)
#:> o
#:> l
#:> l
#:> e
#:> h

### 4.3.3 Generator Expression

Use () to create an annonymous generator function

my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
next(a)
#:> 1
next(a)
#:> 9
sum(a) # sum the power of 6,10
#:> 136

### 4.3.4 Compare to Iterator Class

class PowTwo:
def __init__(self, max = 0):
self.max = max

def __iter__(self):
self.n = 0
return self

def __next__(self):
if self.n > self.max:
raise StopIteration

result = 2 ** self.n
self.n += 1
return result

Obviously, Generator is more concise and cleaner

def PowTwoGen(max = 0):
n = 0
while n < max:
yield 2 ** n
n += 1