# Iterations

• if,elif, else Statements
• for Loops
• while Loops
• range() functions
• break and continue statements
• pass statement
• enumerate
• comprehesions

## if Statements

• The colon (:) separates the header of the compound statement from the body.
• The line after the colon must be indented. It is standard in Python to use four spaces for indenting.
• All lines indented the same amount after the colon will be executed whenever the BOOLEAN_EXPRESSION is true.

#Python if Statement Syntax

```if test expression: statement(s)```

``````if 1 < 2:
print('One Greater Than Two')
``````
``````# If the number is positive, we print an appropriate message

num = 3
if num > 0:
print(num, "is a positive number.")
print("This is always printed.")

num = -1
if num > 0:
print(num, "is a positive number.")
print("This is also always printed.")
``````

## if…else Statement

• It is frequently the case that you want one thing to happen when a condition it true, and something else to happen when it is false

# Syntax of if…else

```if test expression: Body of if else: Body of else```

``````if 1 < 2:
print('first')
else:
print('last')
``````
``````# Program checks if the number is positive or negative
# And displays an appropriate message

num = 3

# Try these two variations as well.
# num = -5
# num = 0

if num >= 0:
print("Positive or Zero")
else:
print("Negative number")
``````

## if…elif…else Statement

#Syntax of if…elif…else

```if test expression: Body of if elif test expression: Body of elif else: Body of else```

Example of if…elif…else

``````if 1 == 2:
print('first')
elif 3 == 3:
print('middle')
else:
print('Last')
``````
``````# In this program,
# we check if the number is positive or
# negative or zero and
# display an appropriate message

num = 3.4

# Try these two variations as well:
# num = 0
# num = -4.5

if num > 0:
print("Positive number")
elif num == 0:
print("Zero")
else:
print("Negative number")
``````

## Nested if statements

Python Nested if Example

Source Code: Using Nested `if`

``````# In this program, we input a number
# check if the number is positive or
# negative or zero and display
# an appropriate message
# This time we use nested if

num = float(input("Enter a number: "))
if num >= 0:
if num == 0:
print("Zero")
else:
print("Positive number")
else:
print("Negative number")
``````

## Practice

Python Program to Check if a Number is Positive, Negative or 0

Source Code: Using `if...elif...else`

``````num = float(input("Enter a number: "))
if num > 0:
print("Positive number")
elif num == 0:
print("Zero")
else:
print("Negative number")
# Here, we have used the if...elif...else statement. We can do the same thing using nested if statements as follows.
``````

Python Program to Check if a Number is Odd or Even

``````# Python program to check if the input number is odd or even.
# A number is even if division by 2 give a remainder of 0.
# If remainder is 1, it is odd number.

num = int(input("Enter a number: "))
if (num % 2) == 0:
print("{0} is Even".format(num))
else:
print("{0} is Odd".format(num))
``````

Python Program to Check Leap Year

A leap year is exactly divisible by 4 except for century years (years ending with 00). The century year is a leap year only if it is perfectly divisible by 400. For example,

``````# Python program to check if the input year is a leap year or not

year = 2000

# To get year (integer input) from the user
# year = int(input("Enter a year: "))

if (year % 4) == 0:
if (year % 100) == 0:
if (year % 400) == 0:
print("{0} is a leap year".format(year))
else:
print("{0} is not a leap year".format(year))
else:
print("{0} is a leap year".format(year))
else:
print("{0} is not a leap year".format(year))
``````

## Iterables and Iterators

Function Description
`range()` Generates a range of integer values
`enumerate()` Returns a list of tuples containing indices and values from an iterable
`iter()` Returns an iterator object
`next()` Retrieves the next item from an iterator

source: https://realpython.com/python-data-types/#strings

## while Loops

• iterate over a block of code as long as the test expression (condition) is true.
• The while loop can be terminated with a `break` statement.

Syntax of while Loop in Python

```while test_expression: Body of while```

• In while loop, test expression is checked first. The body of the loop is entered only if the `test_expression` evaluates to `True`. After one iteration, the test expression is checked again. This process continues until the `test_expression` evaluates to `False`.
• Counter is must in while block to iterate the statements
``````i = 1
while i < 5:
print('i is: {}'.format(i))
i = i+1
``````
``````# Program to add natural
# numbers upto
# sum = 1+2+3+...+n

# To take input from the user,
# n = int(input("Enter n: "))

n = 10

# initialize sum and counter
sum = 0
i = 1

while i <= n:
sum = sum + i
i = i+1    # update counter

# print the sum
print("The sum is", sum)
``````

Ex: While loop using list and get sum

``````l = [3,4,7,9,1]
i = 0
sum = 0
while i < len(l):
sum = sum+l[i]
i = i+1    # update counter

# print the sum
print("The sum is", sum)
``````

while loop with else

``````# Example to illustrate
# the use of else statement
# with the while loop

counter = 0

while counter < 3:
print("Inside loop")
counter = counter + 1
else:
print("Inside else")
``````

**Difference between `while` and `IF` **

``````i = 1
if i < 5: # one time check
print('i is: {}'.format(i))
# i = i+1 # manual increment
``````
``````i is: 1
``````
``````# you will get continuoius infinite values (don't run)
i = 1
while i < 5: # infinite iteration
print('i is: {}'.format(i))
# i = i+1 # if we don't provide manual increment
``````

## for Loops

The for loop in Python is used to iterate over a sequence (`list, tuple, string`) or other iterable objects. Iterating over a sequence is called traversal.

#syntax:
```for iterating_var in iterable: statement/statements```

``````seq = [1,2,3,4,5]
``````
``````type(seq)
``````
``````for item in seq:
print(item)
``````
``````for item in seq:
print('Yep')
``````
``````for jelly in seq:
print(jelly+jelly)
``````
``````# Program to find the sum of all numbers stored in a list

# List of numbers
numbers = [6, 5, 3, 8, 4, 2, 5, 4, 11]

# variable to store the sum
sum = 0

# iterate over the list
for val in numbers:
sum = sum+val

# Output: The sum is 48
print("The sum is", sum)
``````
``````""" Loading the Dishwasher """

# dirty dishes in the sink
sink = ['bowl','plate','cup']

for dish in sink:
print('Putting {} in the dishwasher'.format(dish))
sink.remove(dish)

# check that the sink is empty
print(sink)
``````
``````Putting bowl in the dishwasher
Putting cup in the dishwasher
['plate']
``````
``````""" Loading the Dishwasher """

# dirty dishes in the sink
sink = ['bowl','plate','cup']

for dish in list(sink):
print('Putting {} in the dishwasher'.format(dish))
sink.remove(dish)

# check that the sink is empty
print(sink)
``````
``````Putting bowl in the dishwasher
Putting plate in the dishwasher
Putting cup in the dishwasher
[]
``````

for loop with else

``````digits = [0, 1, 5]

for i in digits:
print(i)
else:
print("No items left.")
``````

How for loop actually works?
https://www.programiz.com/python-programming/iterator#for-loop

Difference between iterable and iterator
https://www.geeksforgeeks.org/python-difference-iterable-iterator/

``````# Function to check object
# is iterable or not
def iterable(obj):
try:
iter(obj)
return True

except TypeError:
return False

# Driver Code
for element in [34, [4, 5], (4, 5),
{"a":4}, "dfsdf", 4.5]:

print(element, " is iterable : ", iterable(element))
``````
``````34  is iterable :  False
[4, 5]  is iterable :  True
(4, 5)  is iterable :  True
{'a': 4}  is iterable :  True
dfsdf  is iterable :  True
4.5  is iterable :  False
``````

## range()

• generate a sequence of numbers using `range()` function. `range(10)` will generate numbers from 0 to 9 (10 numbers).
• Syntax: `range(start,stop,step size)` ```{py} range(stop) takes one argument. range(start, stop) takes two arguments. range(start, stop, step) takes three arguments. ```
``````range(5)
``````
``````r = range(5)
r
``````
``````range(0, 5)
``````
• To force this function to output all the items, we can use the function `list()`.
``````list(range(5))
``````
``````list(r)
``````
``````[0, 1, 2, 3, 4]
``````

**We can use the range() function in for loops to iterate through a sequence of numbers. **

``````for i in range(5):
print(i)
``````
``````# Output: range(0, 10)
print(range(10))

# Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(10)))

# Output: [2, 3, 4, 5, 6, 7]
print(list(range(2, 8)))

# Output: [2, 5, 8, 11, 14, 17]
print(list(range(2, 20, 3)))
``````

Decrementing With `range()`

``````for i in range(10, 4, -2):
print(i)
``````
``````10
8
6
``````
``````for i in range(10, -6, -2):
print(i)
``````
``````10
8
6
4
2
0
-2
-4
``````

It can be combined with the `len()` function to iterate though a sequence using indexing. Here is an example.

``````# Program to iterate through a list using indexing

genre = ['pop', 'rock', 'jazz']

# iterate over the list using index
for i in range(len(genre)):
print("I like", genre[i])
``````

## enumerate¶

for foo in x loops over the elements of a list and for i in range(len(x)) loops over the indices of a list. What if you want to do both?   Enter the enumerate function, one of Python’s hidden gems:. * `enumerate()` function adds a counter to an iterable
* a tuple is produced with (`counter`, `element`)
* `enumerate(iterable, start=0)` start (optional) — enumerate() starts counting from this number. If start is omitted, 0 is taken as the start.

Given a list, enumerate returns an object which iterates over the indices and the values of the list.   (Like the range() function, it returns an iterable object. To see its contents as a list, we can call list() on it.).

``````list(enumerate(['a', 'b']))
``````
``````[(0, 'a'), (1, 'b')]
``````
``````iterable = [8,-2,13,4]
list(enumerate(iterable, start=2))
``````
``````[(2, 8), (3, -2), (4, 13), (5, 4)]
``````
``````iterable = [8,-2,13,4]
list(enumerate(iterable, start=-2))
``````
``````[(-2, 8), (-1, -2), (0, 13), (1, 4)]
``````
``````l1 = [10,20,32,45,60]
print(list(enumerate(l1)))
for i, j in enumerate(l1):
print(i,j)

``````
``````[(0, 10), (1, 20), (2, 32), (3, 45), (4, 60)]
0 10
1 20
2 32
3 45
4 60
``````
``````for item in enumerate('abcd'):
print(item)
``````
``````(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
``````
``````for i,j in enumerate('abcd'):
print(i,j)
``````
``````0 a
1 b
2 c
3 d
``````
``````# multiply 2 for only odd numbers from a given list
def double_odds(nums):
for i, num in enumerate(nums):
if num % 2 == 1:
nums[i] = num * 2

x = list(range(10))
double_odds(x)
x
``````
``````[0, 2, 2, 6, 4, 10, 6, 14, 8, 18]
``````

We can use this unpacking syntax any time we iterate over a collection of tuples.

``````nums = [
('one', 1, 'I'),
('two', 2, 'II'),
('three', 3, 'III'),
('four', 4, 'IV'),
]

for word, integer, roman_numeral in nums:
print(integer, word, roman_numeral, sep=' = ', end='; ')
``````
``````1 = one = I; 2 = two = II; 3 = three = III; 4 = four = IV;
``````

## The break statement

• The break statement is used to immediately leave the body of its loop. The next statement to be executed is the first one after the loops body ends
``````# Use of break statement inside loop

for val in "string":
if val == "i":
break # after this it  exit from the for loop
print(val)

print("The end")
``````
``````for i in [12, 16, 17, 24, 29]:
if i % 2 == 1:  # if the number is odd
break        # immediately exit the loop
print(i)
print('lucid')
print("done")
``````
``````12
16
lucid
done
``````

## The continue statement

• This is a control flow statement that causes the program to immediately skip the processing of the rest of the body of the loop, for the current iteration. But the loop still carries on running for its remaining iterations(continue = skip this and go next):
``````# Program to show the use of continue statement inside loops

for val in "string":
if val == "i":
continue
print(val)

print("The end")
``````
``````for i in [12, 16, 17, 24, 29, 30]:
if i % 2 == 1:      # if the number is odd
continue        # don't process it
print(i)
print("done")
``````
``````12
16
24
30
done
``````

## Python pass statement

• In Python programming, pass is a null statement, it results into no operation (NOP).

Example: pass Statement

``````# pass is just a placeholder for
# functionality to be added later.
sequence = {'p', 'a', 's', 's'}
for val in sequence:
pass
``````

## Comprehensions

• for faster execution
• shorter and effective codes (single line)
• involves 3 things – iteration, conditional filtering and processing.

#Syntax of List Comprehension

`[ expression for item in iterable ]` or `[expression for item in list]`

### comprehension for lists ``````h_letters = [letter for letter in 'human' ]
print( h_letters)
``````
``````['h', 'u', 'm', 'a', 'n']
``````
``````lst = [i for i in range(11)]
print(lst)
``````
``````[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
``````
``````squares = [x * x for x in range(10)]
print(squares)
``````
``````[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
``````
``````cube_list = [i**3 for i in range(50, 101)]
print(cube_list)
``````
``````[125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379, 216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509, 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, 512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, 729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299, 1000000]
``````

IF condition With List Comprehension

#Syntax of List Comprehension
`[ expression for item in iterable if condition ]`

``````even_squres = [x * x for x in range(10) if x % 2 == 0]
print(even_squres)
``````
``````[0, 4, 16, 36, 64]
``````
``````[x for x in 'MATHEMATICS' if x in ['A','E','I','O','U']]
``````
``````['A', 'E', 'A', 'I']
``````
``````cart_1 = [1, 8, 29, 34, 58, 74, 88, 99]
cart_2 = [3, 8, 31, 36, 58, 77, 88, 93]
# List comprehension version
cashier_5 = [item for item in cart_1 if item in cart_2]
cashier_5
``````
``````[8, 58, 88]
``````

Nested IF with List Comprehension

``````num_list = [y for y in range(100) if y % 2 == 0 if y % 5 == 0]
print(num_list)
``````
``````[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
``````

nested for loop

``````print ([x+y for x in 'get' for y in 'set' if x != 't' and y != 'e' ])
``````
``````['gs', 'gt', 'es', 'et']
``````

if…else With List Comprehension

``````obj = ["Even" if i%2==0 else "Odd" for i in range(10)]
print(obj)
``````
``````['Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd']
``````

Nested List Comprehensions

``````
list_of_list = [[1,2,3],[4,5,6],[7,8]]

# Flatten `list_of_list`
[y for x in list_of_list for y in x]

``````
``````[1, 2, 3, 4, 5, 6, 7, 8]
``````

### comprehension for dictionaries

``````squares3_dict = {i: i**2 for i in range(30) if i % 3 == 0}
squares3_dict
``````
``````{0: 0,
3: 9,
6: 36,
9: 81,
12: 144,
15: 225,
18: 324,
21: 441,
24: 576,
27: 729}
``````
``````capitals = {'United States': 'Washington, DC','France': 'Paris','Italy': 'Rome'}
capitals_bycapital = {capitals[key]: key for key in capitals}
capitals_bycapital
``````
``````{'Paris': 'France', 'Rome': 'Italy', 'Washington, DC': 'United States'}
``````

### comprehension for sets

• Set comprehensions are created in much the same way as dictionary comprehensions.
``````# You can create a normal set like this:
my_list = [1, 2, 2, 3, 4, 5, 5, 7, 8]
my_set = set(my_list)
my_set
``````
``````{1, 2, 3, 4, 5, 7, 8}
``````
``````# Now let’s rewrite this code to use a set comprehension:
my_list = [1, 2, 2, 3, 4, 5, 5, 7, 8]
my_set = {x for x in my_list}  # uses the curly braces that the dictionary comprehension has.
my_set
``````
``````{1, 2, 3, 4, 5, 7, 8}
``````

## Errors Handling: try except and finally

Types of Errors * Syntax Errors * Runtime Errors * Logic Errors

``````#Syntax Errors
x = 42
y = 206
if x == y
print('Equal')
``````
``````#Runtime Errors
x = 42
y = 0
print(x/y)
``````
``````# once we execute the below script we will encounter an error
print(10/0)
``````
``````---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-1-364d6fd1cf4d> in <module>()
1 # once we execute the below script we will encounter an error
----> 2 print(10/0)

ZeroDivisionError: division by zero
``````
``````#Logic Errors
x = 206
y = 42
if x<y:
print(str(x) + 'is greater than' + str(y))
``````
``````# we didn't get desired output
x = 206
y = 42
if x<y:
print(str(x) + 'is greater than' + str(y))
``````
• To overcome this error we can tell python to handle this error and throw some message when ever it happens otherwise execute succesfully*
``````try:
print(10/0)
except:
print("Can't divide a number by zero")

``````
``````Can't divide a number by zero
``````
``````try:
print(10/2)
except:
print("Can't divide a number by zero")
``````
``````5.0
``````

Handling Exceptions with Try/Except/Finally

``````     try:
...     print("in the try block")
...     print(1/0)                   # You put the unsafe code in the try: block.
... except:
...     print("In the except block") # You put the fall-back code in the Except: block
... finally:
...     print("In the finally block") # The final code is kept in the Finally: block
...
``````
``````in the try block
In the except block
In the finally block
``````

Raising exceptions for a predefined condition
For example, if you want to limit the user-input to only positive integers, raise an exception.

``````try:
user = int(input('Please enter a number? '))
if user < 0:
else:
print("user input: %s" % user)
except ValueError as e:
print(e)
``````
``````Please enter a number? 15
user input: 15
``````

## Memory Management in Python

Memory management in Python involves a private heap containing all Python objects and data structures. Interpreter takes care of Python heap and that the programmer has no access to it. - The allocation of heap space for Python objects is done by Python memory manager. The core API of Python provides some tools for the programmer to code reliable and more robust program. - Python also has a build-in garbage collector which recycles all the unused memory. When an object is no longer referenced by the program, the heap space it occupies can be freed. The garbage collector determines objects which are no longer referenced by the sprogram frees the occupied memory and make it available to the heap space.

``````x  =10
print(type(x))

y = x
if (id(x) == id(y)):
print("x and y refer to same object")

x = x+1
if (id(x) != id(y)):
print("x and y refer to different object")

z = 10
if (id(y) == id(z)):
print("y and z point to the same object")
else:
print("y and z point to different same object")
``````
``````<class 'int'>
x and y refer to same object
x and y refer to different object
y and z point to the same object
``````

Reason:
Everything is object in Python
Python optimizes memory unilization by allocating the same oject reference to a new variable if object already exiists with same value

Comparison with Other Languages: source: https://www.youtube.com/watch?v=arxWaw-E8QQ

Sources & References
https://www.programiz.com
http://www.openbookproject.net/books/bpp4awd/ch04.html

Further Resources
https://www.geeksforgeeks.org/loops-in-python/