lista = [1, 2, 3]
for el in lista:
    print(el)

lista = [1, 2, 3]
iterator = iter(lista)
print(iterator)
print(type(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

class CustomIterator():
    def __init__(self):
        self.liczby = [1, 2, 3]
        self.index = -1
        
    def __iter__(self):
        print("pobieram iterator")
        return self
        
    def __next__(self):
        print("pobieram kolejna wartosc")
        self.index = self.index + 1
        if (self.index == len(self.liczby)):
            raise StopIteration
        else:
            return self.liczby[self.index]

for x in CustomIterator():
    print(x)

import random

class CustomRandomIterator():
    def __init__(self):
        self.liczby = random.sample(range(10), 5)
        
    def __iter__(self):
        return iter(self.liczby)

for x in CustomRandomIterator():
    print(x)

class CustomCollection():
    def __init__(self, collection):
        self.collection = collection
        
    def __iter__(self):
        return CustomIterator(self.collection)
    

class CustomIterator():
    def __init__(self, collection):
        self.collection = collection
        self.index = -1
        self.stop_index = len(collection)
        
    def __next__(self):
        self.index = self.index + 1
        if (self.index == self.stop_index):
            raise StopIteration
        else:
            return self.collection[self.index]

for x in CustomCollection(list(range(5))):
    print(x)

class CustomCollection():
    def __init__(self, collection):
        self.collection = collection
        
    def __iter__(self):
        return CustomIterator(self.collection)
    

class CustomIterator():
    def __init__(self, collection):
        self.collection = collection
        self.index = -1
        self.stop_index = len(collection)
        
    def __next__(self):
        self.index = self.index + 1
        if (self.index == self.stop_index):
            raise StopIteration
        else:
            return self.collection[self.index]

iterator = iter(CustomCollection(list(range(10))))
print(type(iterator))
print(next(iterator))

"""
Explanation: Iteratory

relacja konsument - producent
obiekt, który wie jak poruszać się po kolejnych elementach kolekcji
implementuje protokół iteratora

iteratory na tej samej kolekcji są niezależne od siebie


brak możliwości uruchomienia iteratora od początku

implementuje funkcję __next__, która zwraca strumień danych
gdy przejdzie do końca kolekcji rzuca wyjątkiem StopIteration
implementuje funkcję __iter__, która zwraca iterator
End of explanation
"""


import random
def frange(start, stop, step=1):
    x = start
    while x < stop:
        yield x
        x += step
        
for el in frange(1, 4, 0.5):
    print(el)

import random
def random_numbers_generator(quantity=5, start=0, stop=10):
    while quantity > 0:
        yield random.randrange(start, stop)
        quantity -= 1
        
for el in random_numbers_generator():
    print(el)

import random
def random_numbers_generator(quantity=5, start=0, stop=10):
    while quantity > 0:
        yield random.randrange(start, stop)
        quantity -= 1
        
for el in random_numbers_generator():
    print(el)

def only_unique(items):
    seen = set()
    for item in items:
        if item in seen:
            continue
        else:
            seen.add(item)
            yield item
            
list(only_unique('aaaabbbcccddeeefff'))

from collections import deque
import random

class RandomNumbersGenerator():
    
    def __init__(self, count=5, historylen=3):
        self._count = count
        self.history = deque(maxlen=historylen)
        
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        pass
    
    def __iter__(self):
        while self._count > 0:
            random_number = random.randrange(10)
            self.history.append(random_number)
            self._count = self._count - 1
            yield random_number

with RandomNumbersGenerator(count=10) as rng:
    for el in iter(rng):
       print(el, rng.history)

from collections import Iterable
def flatten(items):
    for item in items:
        if isinstance(item, Iterable):
            yield from flatten(item)
        else:
            yield item

x = [1, 2, 3, [4, 5, [6, 7], 8], 9]
for el in flatten(x):
    print(el)

"""
Explanation: Generatory

implementuje protokół iteratora
funkcja zawierająca słowo kluczowe yield
yield zatrzymuje wykonanie funkcji, przy ponownym wywołaniu uruchamia wykonywanie od słowa yield
End of explanation
"""


