<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Yábir García Benchakhtir
Step1:  Ejercicio 1
1. Calcula la b-ésima y la a-ésima cifra decimal de π.
Step2:  Convenimos que la posición decimal de las decimas está indicada con el 0.
Step3:  2. Calcula el conjunto de los divisores naturales del número a ¿Cuánto vale la suma de todos ellos?
Step4:  3. Calcula el mayor número primo menor que a y el menor número primo mayor que a. Calcula también la suma de todos los primos menores que a.
Step5:  Luego el mayor primo < a es
Step6:  Vamos a calcular ahora el menor primo > que a. Para ello vamos a recurrir a itertools que es una librería del nucleo de python que nos proporciona funciones para hacer algo próximo a la programación funcional
Step7:  Luego el siguiente primo mayor que a es 937 
4. Calcula el número de ceros en que acaba el factorial de (1000+a)
Una manera de calcular el factorial es usar la librería que trae python.
Step8:  Tambíen podemos hacernos nuestro propio factorial
Step10:  Vemos que da el mismo resultado
El numero de ceros en los que acaba el factorial es
Step11:  Ejercicio 2 Un número natural n es perfecto si la suma de sus divisores distintos de él vale n. Halla los números perfectos menores que 10000.
Ya tenemos una función para calcular los divisores de un número n
Step12:  La suma de los divisores es (incluyendo al 1 y omitiendo el propio número)
Step13:  Los números perfectos entre los 1000 primeos naturales son
Step14:  En total son
Step15:  Ejercicio 3
Demostremos que si $2^{p} -1 es primo necesariamente p es primo.
Vamos a demostrarlo usando el contrarecíproco. 
Supongamos que $p$ no es primo. Por tanto lo podemos expresar como producto de dos números. Sean $m,n \in \mathbb{N}$ tal que $2 \le m,n < p$ y $mn=p$. Tenemos que demostar que "Si $p$ no es primo entonces $2^{p}-1$ tampoco es primo".
Sea $p = mn$ para $m,n \in \mathbb{N}$ tales que $2 \le m,n < p$
$2^{mn}-1 = (2^m)^n -1 = (2^m -1 )((2^m)^{n-1} + (2^m)^{n-1} + ...  + 2^m + 1)$
por nuestra hipótesis tenemos
$2 \le 2^m -1 < 2^p -1$
Así $2^m -1$ divide a $2^p -1$ y esto nos dice que $2^p -1$ es compuesto.
Hemos demostrado que si p no es primo entonces $2^p -1$ tampoco lo es. Podemos afirmar pues
Step17:  Como comprobar si un primo de la forma $2^{p} -1$ es una tarea muy pesada cuando p crece vamos a usar el algoritmo de Lucas-Lehmer.
Step18:  En esta lista tenemos tuplas de los primeros 258 números y si son primos de Mersenne. Los que lo cumplen son
Step19:  Luego Mersenne se había equivocado y la lista correcta es la anterior.
Vemos que no es cierto que para todo $p$ primo $2^{p} -1$ sea primo.
Ejercicio 4 La conjetura de Goldbach afirma que todo número natural par mayor que 3 se puede poner como la suma de dos números primos
1. Escribe (1000+2a) como suma de dos primos. Halla todas las descomposiciones posibles de (1000+2a) como suma de dos primos. ¿De cuántas formas se puede descomponer (1000+2*a) como suma de dos primos?.
Step20:  creamos una lista de primos hasta 1000+2a
Step21:  Soluciones
Step22:  Todas las descomposiciones como suma de primos
Step23:  Numero de formas
Step24:  2. Define la función goldbach(n) que devuelva una pareja de primos que sumen n. Define la función goldbachtotal(n) que devuelva la lista de todas las parejas de primos que sumen n. Define dgoldbach(n) que devuelva el número de descomposiciones de Goldbach que admite n.
Step25:  Ejercicio 5
Step26:  Al intentar calcular el valor para ackermann(4,2) se desborda la pila por lo que hay que buscar otra manera de resolver el problema.
Propiedades de la función de Ackermann
<ASSISTANT_TASK:>
Python Code:
numero = 78277485
a = numero % 1000; a
b = numero % 100; b
Explanation: Yábir García Benchakhtir
End of explanation
from mpmath import mp #Librería usada para modiuficar la precision decimal de PI
from math import sqrt
mp.dps = 1000 #Establecer la precisión que queremos
pi_1000 = mp.pi
print(pi_1000)
Explanation: Ejercicio 1
1. Calcula la b-ésima y la a-ésima cifra decimal de π.
End of explanation
besima = str(pi_1000)[b+2]
print(besima)
aesima = str(pi_1000)[a+2]
print(aesima)
Explanation: Convenimos que la posición decimal de las decimas está indicada con el 0.
End of explanation
#Generador de python que devuelve los divisores de un número n
def divisores(n):
    if n < 1:
        raise ValueError("El número ha de ser natural y mayor que 0")
    div = [] #Complementos de numeros
    for numero in range(1, int(sqrt(n) +1 )):
        if n%numero == 0:
            yield numero #Si es un divisor lo devolvemos
            #En la siguiente iteración comprobamos si su cuadrado
            #no es n. Simpre va a ser <= n ya
            #que buscamos hasta la raiz del numero
            if numero*numero != n:
                #Si no es lo añadimos como divisor
                div = div + [int(n/numero)]
    for divisor in div:
        yield divisor
list(divisores(a))
# La suma de los divisores es
sum( list(divisores(a)) )
# Un ejemplo para ver como sería con otro número
list( divisores( 24 ) )
sum( list( divisores( 24 ) ) )
Explanation: 2. Calcula el conjunto de los divisores naturales del número a ¿Cuánto vale la suma de todos ellos?
End of explanation
primos = [2]
#Una lista para almacenar los primos que encontremos
def es_primo(n):
    if n >= 2:
        for y in range(2,int(sqrt(n))+1):
            if not ( n % y ):
                return False
    else:
        return False
    
    return True
        
for i in range(1,920):
    if es_primo(i):
        primos.append(i)
print(primos)
Explanation: 3. Calcula el mayor número primo menor que a y el menor número primo mayor que a. Calcula también la suma de todos los primos menores que a.
End of explanation
primos [-1]
Explanation: Luego el mayor primo < a es
End of explanation
from itertools import takewhile, count
[x for x in takewhile(lambda x: not es_primo(x), count(a+1))][-1]+1
Explanation: Vamos a calcular ahora el menor primo > que a. Para ello vamos a recurrir a itertools que es una librería del nucleo de python que nos proporciona funciones para hacer algo próximo a la programación funcional
End of explanation
from math import factorial
#factorial(1000+a)
Explanation: Luego el siguiente primo mayor que a es 937 
4. Calcula el número de ceros en que acaba el factorial de (1000+a)
Una manera de calcular el factorial es usar la librería que trae python.
End of explanation
from operator import mul
from functools import reduce
def factorial_mio(n):
    return reduce(mul, range(1,n+1))
factorial(1000+a) == factorial_mio(1000+a)
Explanation: Tambíen podemos hacernos nuestro propio factorial
End of explanation
Para buscar el número de 0 se ha procedido a convertir el número en una cadena de caracteres
invertir la posicion de los digitos, el último pasa a ser el primero y así con el resto de dígitos,
por último se recorre la cadena hasta que se encuentra un dígito distinto de 0
total_ceros = 0
distinto_encontrado = False
numero = str( factorial_mio(1000+a) )[::-1]
while not distinto_encontrado:
    if numero[total_ceros] == '0':
        total_ceros += 1
    else:
        distinto_encontrado = True
    
total_ceros
Explanation: Vemos que da el mismo resultado
El numero de ceros en los que acaba el factorial es:
End of explanation
def divisores(n):
    if n < 1:
        raise ValueError("El número ha de ser natural y mayor que 0")
    div = [] #Complementos de numeros
    for numero in range(1, int(sqrt(n) +1 )):
        if n%numero == 0:
            yield numero #Si es un divisor lo devolvemos
            #En la siguiente iteración comprobamos si su cuadrado
            #no es n. Simpre va a ser <= n ya
            #que buscamos hasta la raiz del numero
            if numero*numero != n:
                #Si no es lo añadimos como divisor
                div = div + [int(n/numero)]
    for divisor in div:
        yield divisor
Explanation: Ejercicio 2 Un número natural n es perfecto si la suma de sus divisores distintos de él vale n. Halla los números perfectos menores que 10000.
Ya tenemos una función para calcular los divisores de un número n
End of explanation
def suma_divisores(n):
    divis = list(divisores(n))
    #Ahora quitamos el propio número
    divis.remove(n)
    return sum(divis)
suma_divisores(24)
Explanation: La suma de los divisores es (incluyendo al 1 y omitiendo el propio número):
End of explanation
[x for x in range(1,1001) if x == suma_divisores(x)]
Explanation: Los números perfectos entre los 1000 primeos naturales son:
End of explanation
len([x for x in range(1,1001) if x == suma_divisores(x)])
Explanation: En total son
End of explanation
primos_en_258 = []
for i in range(1,258):
    if es_primo(i):
        primos_en_258.append(i)
print(primos_en_258)
Explanation: Ejercicio 3
Demostremos que si $2^{p} -1 es primo necesariamente p es primo.
Vamos a demostrarlo usando el contrarecíproco. 
Supongamos que $p$ no es primo. Por tanto lo podemos expresar como producto de dos números. Sean $m,n \in \mathbb{N}$ tal que $2 \le m,n < p$ y $mn=p$. Tenemos que demostar que "Si $p$ no es primo entonces $2^{p}-1$ tampoco es primo".
Sea $p = mn$ para $m,n \in \mathbb{N}$ tales que $2 \le m,n < p$
$2^{mn}-1 = (2^m)^n -1 = (2^m -1 )((2^m)^{n-1} + (2^m)^{n-1} + ...  + 2^m + 1)$
por nuestra hipótesis tenemos
$2 \le 2^m -1 < 2^p -1$
Así $2^m -1$ divide a $2^p -1$ y esto nos dice que $2^p -1$ es compuesto.
Hemos demostrado que si p no es primo entonces $2^p -1$ tampoco lo es. Podemos afirmar pues:
Si $2^p -1$ es primo, entonces $p$ es primo. 
Sabiendo esto, primero buscamos los primos entre los primeros 257 numeros.
End of explanation
def LucasLehmer(p):
    
    Este algoritmo determina si 2**p-1 es primo para p >= 2
    
    if p == 2:
        return True
    
    s = 4
    M = 2**p -1
    for i in range(3, p+1):
        s = (s**2 -2) % M
        
    return s == 0
lista = []
for i in primos_en_258:
    lista.append( (i, LucasLehmer(i) ) )
print(lista)
Explanation: Como comprobar si un primo de la forma $2^{p} -1$ es una tarea muy pesada cuando p crece vamos a usar el algoritmo de Lucas-Lehmer.
End of explanation
print([x[0] for x in lista if x[1] == True ])
Explanation: En esta lista tenemos tuplas de los primeros 258 números y si son primos de Mersenne. Los que lo cumplen son
End of explanation
numero = 1000+2*a
print(numero)
Explanation: Luego Mersenne se había equivocado y la lista correcta es la anterior.
Vemos que no es cierto que para todo $p$ primo $2^{p} -1$ sea primo.
Ejercicio 4 La conjetura de Goldbach afirma que todo número natural par mayor que 3 se puede poner como la suma de dos números primos
1. Escribe (1000+2a) como suma de dos primos. Halla todas las descomposiciones posibles de (1000+2a) como suma de dos primos. ¿De cuántas formas se puede descomponer (1000+2*a) como suma de dos primos?.
End of explanation
def lista_primos(n):
    lista = []
    for i in range(1,n+1):
        if es_primo(i):
            lista.append(i)
    
    return lista
print(lista_primos(numero))
def suma_primos(numero):
    primos = lista_primos(numero)
    indice_i = 0
    indice_m = len(primos) - 1
    while indice_i <= indice_m:
        suma = primos[indice_i] + primos[indice_m]
        if suma < numero:
            indice_i += 1
        else:
            if suma == numero:
                yield primos[indice_i], primos[indice_m]
            indice_m -= 1
Explanation: creamos una lista de primos hasta 1000+2a
End of explanation
list(suma_primos(numero))[0]
Explanation: Soluciones:
1000+2a como suma de dos primos
End of explanation
print(list(suma_primos(numero)))
Explanation: Todas las descomposiciones como suma de primos
End of explanation
len(list(suma_primos(numero)))
Explanation: Numero de formas
End of explanation
def golbach(n):
    primos = lista_primos(n)
    sumas = list(suma_primos(n))
    return sumas[0]
golbach(12)
def dgoldbach(n):
    primos = lista_primos(n)
    sumas = list(suma_primos(n))
    return len(sumas)
dgoldbach(24)
Explanation: 2. Define la función goldbach(n) que devuelva una pareja de primos que sumen n. Define la función goldbachtotal(n) que devuelva la lista de todas las parejas de primos que sumen n. Define dgoldbach(n) que devuelva el número de descomposiciones de Goldbach que admite n.
End of explanation
def ackermann(m, n):
    if m == 0:
        return n+1
    if n == 0:
        return ackermann(m-1, 1)
    return ackermann(m-1, ackermann(m, n-1))
ackermann(3,2)
Explanation: Ejercicio 5
End of explanation
numero = 2
n = 2
for i in range(n+2):
    numero = numero**2
print(numero)
Explanation: Al intentar calcular el valor para ackermann(4,2) se desborda la pila por lo que hay que buscar otra manera de resolver el problema.
Propiedades de la función de Ackermann:
A(1, n) = n + 2
A(2, n) = 2 n + 3
A(3, n) = 2n + 3 - 3
A(4, n) = $2^{2^{2^{...}}} − 3$ donde el primer sumando es una potencia anidada de n + 3 niveles.
Así para el caso de ackerman(4,2) el resultado sería
End of explanation
<END_TASK>