'''
- "Interators" r objects that can traverse an "Iterable"
- Examples of Iterable objects are tuples, lists, strings, arrays, etc.
- list, dictionary, set, tuple, are all iterable classes.

- we can create iterator using iter() built-in-meth
- traversal is done using next(iterator)
- StopIteration exception is raised when no more elements are left
  after done traversing.
  
  An iterator object has two special methods that can be used with it,
  called iter() and next().
'''

# iterable = 
''' An iterable is an object that returns an iterator if iter() is called on it '''
# iterator
''' Iterators are objects that are either returned by iter() (as shown below),
    or they can be made by the programmer using a generator 
    
    They have three key properties:

    1. They return an object when next() is called on it.
    2. If there is no object to return, it will raise a StopIteration error.
    3. Iteration only happens once.
        If you got an iterator for a list that contains 5 numbers,
        and you called next() 5 times,
        after that, the iterator is of no use.
        That is, to re-iterate over the same list,
        a new and fresh iterator will be required.
'''

a = "Hi" # string Iterable
it = iter(a) # str interator created using iter() meth
print(it)
print(type(it)) #<class 'str_iterator'>

print(next(it)) # H
print(next(it)) # i
# print(next(it)) # StopIteration exception
# ---------------------------------------------------------------------
l = [1, 2] # list Iterable
it = iter(l) # list interator created
print(it)
print(type(it)) # <class 'list_iterator'>

print(next(it))
print(next(it))
# print(next(it)) # StopIteration exception
# ---------------------------------------------------------------------

# *******Constructing our own Iterator in Python***********
print('_______own iterator__________\n')
'''
Any iterator object has a countable number of elements, that can be traversed.
But how can we make our own iterator? We need to create our own Class.

In Python, constructing any iterator involves a protocol called the Iterator Protocol.

This protocol contains two specific methods, called __iter__() and __next__(),
similar to the general iterator methods,
but since they are inside a class, it is prefixed and suffixed with this '__' symbol,
to show the distinction.

The iter() and next() methods internally call these methods,
and hence, to make the iterator, we need to define our own __iter__() and __next__()
methods inside our class.

Let’s create a simple iterator that traverses across a list only
and raises a StopIteration Exception if the count of elements is greater than 5.
'''

class Myclass:
  def __init__(self):
    self.count = 0
    self.limit = 5
  
  def __iter__(self):
    print('__iter__()')
    return self
    
  def __next__(self):
    print('__next__()')
    if self.count >= self.limit: 
      raise StopIteration
    else:
      self.count+=1
      return self.count
      
# print(Myclass)
obj = Myclass()
# print(obj)
it = iter(obj)
# print(it)

for i in range(5):
  print(next(it)) # print(next()) stmt for 5 times
  
# print(next(it)) # StopIteration

'''We’ve made our own iterator!'''



# *****______________________ generators in python______________________******

print('_______ generators in python __________\n')
'''
Generators in python are functions that create an iterator.

Generators in Python are powerful tools to make custom iterators
to generate a sequence of objects

The generator follows the same syntax as a function,
but instead of writing return, we write yield whenever it needs to return something.
'''

# __________Creating a generator function
# Let’s say we need to generate the first 10 perfect squares starting from 1.

print('***function with generator***')
def sq():
  for i in range(5):
    yield i*i
    # return i*i
    
res = sq()
# print(res) # generator object
# for i in res:
#   print(i, end=' ')
  
for i in range(5):
  print(next(res))

print('\n***function without generator***')
def sq1():
  for i in range(5):
    return i*i
res1=sq1()    
print(res1) # 0
# for i in res1:
#   print(i, end=' ') # TypeError: 'int' object is not iterable


'''
yield(i * i):
  The most important and noticeable distinction from a normal function in Python.
  This is similar to a return statement in that it returns the generated perfect sq.
  Notice that It's generated because all the perfect sq this function returns are
  generated and not retrieved from memory.
'''
 
# ---------------------------------------------------------------------
sq = [i*i for i in range(1, 11)]
# print(type(sq))

'''
Replacing “[” and “]” with “(” and “)” will instead create a generator
that generates these values.
'''
sq = (i*i for i in range(1, 11))
# print(type(sq))
# ---------------------------------------------------------------------


# https://www.askpython.com/python/examples/generators-in-python

# ***************************************************************************
'''DICTIONARY IN PYTHON'''
 
by

Python Online Compiler

Write, Run & Share Python code online using OneCompiler's Python online compiler for free. It's one of the robust, feature-rich online compilers for python language, supporting both the versions which are Python 3 and Python 2.7. Getting started with the OneCompiler's Python editor is easy and fast. The editor shows sample boilerplate code when you choose language as Python or Python2 and start coding.

Taking inputs (stdin)

OneCompiler's python online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample python program which takes name as input and print your name with hello.

import sys
name = sys.stdin.readline()
print("Hello "+ name)

About Python

Python is a very popular general-purpose programming language which was created by Guido van Rossum, and released in 1991. It is very popular for web development and you can build almost anything like mobile apps, web apps, tools, data analytics, machine learning etc. It is designed to be simple and easy like english language. It's is highly productive and efficient making it a very popular language.

Tutorial & Syntax help

Loops

1. If-Else:

When ever you want to perform a set of operations based on a condition IF-ELSE is used.

if conditional-expression
    #code
elif conditional-expression
    #code
else:
    #code

Note:

Indentation is very important in Python, make sure the indentation is followed correctly

2. For:

For loop is used to iterate over arrays(list, tuple, set, dictionary) or strings.

Example:

mylist=("Iphone","Pixel","Samsung")
for i in mylist:
    print(i)

3. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while condition  
    #code 

Collections

There are four types of collections in Python.

1. List:

List is a collection which is ordered and can be changed. Lists are specified in square brackets.

Example:

mylist=["iPhone","Pixel","Samsung"]
print(mylist)

2. Tuple:

Tuple is a collection which is ordered and can not be changed. Tuples are specified in round brackets.

Example:

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)

Below throws an error if you assign another value to tuple again.

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)
myTuple[1]="onePlus"
print(myTuple)

3. Set:

Set is a collection which is unordered and unindexed. Sets are specified in curly brackets.

Example:

myset = {"iPhone","Pixel","Samsung"}
print(myset)

4. Dictionary:

Dictionary is a collection of key value pairs which is unordered, can be changed, and indexed. They are written in curly brackets with key - value pairs.

Example:

mydict = {
    "brand" :"iPhone",
    "model": "iPhone 11"
}
print(mydict)

Supported Libraries

Following are the libraries supported by OneCompiler's Python compiler

NameDescription
NumPyNumPy python library helps users to work on arrays with ease
SciPySciPy is a scientific computation library which depends on NumPy for convenient and fast N-dimensional array manipulation
SKLearn/Scikit-learnScikit-learn or Scikit-learn is the most useful library for machine learning in Python
PandasPandas is the most efficient Python library for data manipulation and analysis
DOcplexDOcplex is IBM Decision Optimization CPLEX Modeling for Python, is a library composed of Mathematical Programming Modeling and Constraint Programming Modeling