<jupyter_start><jupyter_text>Iterables and GeneratorsOne nice thing about a list is that you can retrieve specific elements by their indices. But you don’t always need this! A list of a billion numbers takes up a lot of memory. If you only want the elements one at a time, there’s no good reason to keep them all around. If you only end up needing the first several elements, generating the entire billion is hugely wasteful.Often all we need is to iterate over the collection using for and in. In this case we can create generators, which can be iterated over just like lists but generate their values lazily on demand.One way to create generators is with functions and the yield operator:<jupyter_code>"""Concepto del generador (es un iterable):
- El generador no calcula todos los numeros de la secuencia (que puede ser infinita)
- Cada vez que se llama al next, ejecutara una vez el 'yield' de la funcion
- Cuando transofmras el generador a otro tipo, por ejemplo list, llama a nexxt todas las veces posibles hasta terminar el bucle 
- Es util cuando tienes que caulcular una secuenca muy larga de numeros que solo vas a necesitar de 1 en 1 (con el next)"""
#cuando tengo que crear algo que tiende al infinito lo vamos a ver ahor. el concepto generador es un concepto de python que me va adar x numeros cuando yo se lo pida 
def create_list(n):
    lista = []
    for i in range(n):
        lista.append(i)
    return lista
def generate_range(n):
    i = 0
    while i < n: #hasta que el contador no esa mayor que n va a seguir imprimiendo numeros

        yield i   # every call to yield produces a value of the generator yield es un return que no va a parar la funcion.El concepto de generador, es que te guarda el ultimo numero y te calcula el siguiente nada mas. La diferencia con una lista o un while sin generador, es que de la otra manera te lo guarda todo. va devolviendo numeros a medida que los vaya retornando. ejemplo si le pides 10 numeros, va al primer numero te lo devuelve luego el segundo y te lo devuelve y asi no los calcula todos y te los devuelve
        #imprte una vez cada vez que les llame 
        i += 1
#ejemplo
%%time
for i in create_list(100000):
    print(i, end="\r") #este barra erre sirve como xa hacer un print en la misma linea es decir no los imprime todos en linea 
generate_range(n=100000) 
%%time
for i in generate_range(n=100000): #puedo recorrer el generador 
    print(i, end="\r")
generate_range(10)
for i in generate_range(10):
    print(f"i: {i}")
x=2
print(f"X: {x}")
def check_prime(number):    #si el numero es primo 
    for divisor in range(2, int(number)//2 +2):
        if number % divisor == 0:
            return False
    return True
        
def primes(n):    #esta funcion va a estar recorriendo numeros, en este caso hast 100 mirar casilla de abajo y los va a imprimir hasta que el 100 sea mayor que el contador number.  y si es un numero primo, con la funcion de arriba, nos va a yieldear los primos
    number = 1
    while number < n:        
        number += 1        
        if check_prime(number=number):           
            yield number
generator = primes(n=100) #esta funcion retorna un generador y tarda poco porque solo se va queando con el ultimo numero
generator
type(generator)

for i in range(3): # si creo un bucle que se ejecuta 3 veces la funcion next va a irtelo printeando de uno en uno. cada vez que los llames de uno en uno, en este caso de 3 en tres porque es rango 3
    print(next(generator))
print(next(generator))
list(generator) #como podemos ver, el rango te da los 3 primeros, el printeo de next generator, da el siguiente y la lista desde los siguientes 
lista_primos=[]
for i in range(10):
    primo=next(generator)
    lista_primos.append(primo)
    print(primo)

lista_primos #me  guarda los 10 ultimos numeros que salen en la lista, mas los anterirores (los guardas en una lista)  y me printea los primos<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.052591463414634144, 'fraction_numerical': 0.013465447154471545, 'mean_word_length': 3.525287356321839, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '43360', 'license': 'MIT', 'n_tokens_mistral': 1228, 'n_tokens_neox': 1180, 'n_words': 628, 'path': 'week4_EDA_np_pd_json_apis_regex/day4_gen_annotation_eda/theory/python/generators/Iterables_Generators.ipynb', 'repo_name': 'PabloEduardoMartinezPicazo/Bootcamp-DataScience-2021', 'size': '3936'}