# Correction TP pour

## Imports

import numpy as np
    
## Exercices 1-2-3-4 Suites récurrentes
def suite_arithmetique(n,u0,r):
    """Cette fonction calcule et renvoie le n−ième terme de la suite arithmetique (u_n) de premier terme u_0 = u0 et de raison r """
    u=u0 # Initialisation
    for k in range(1,n+1) :
        u=u+r # ou u+=r
    return u
    
def suite_geometrique(n,u0,q):
    """Cette fonction calcule et renvoie le n−ième terme de la suite géométrique (u_n) de premier terme u_0 = u0 et de raison q """
    u=u0 # Initialisation
    for k in range(1,n+1) :
        u=q*u # ou u*=q
    return u

def suite_arithmetico_geometrique(n,u0,a,b):
    """Cette fonction calcule et renvoie le n−ième terme de la suite arithmético-géométrique (u_n) de premier terme u_0 = u0 et pour n>=0, u_(n+1)=a*u_n+b """
    u=u0 # Initialisation
    for k in range(1,n+1) :
        u=a*u+b 
    return u
    
def recudouble(n,a,b,alpha,beta):
    """ Cette fonction calcule et renvoie la valeur de u_n pour la suite définie par récurrence, u_0=alpha, u_1=beta et u_(n+2)=au_(n+1)+bu_n  """
    u=alpha
    v=beta # initialisation
    for k in range(2,n+1):
        w=a*v+b*u # Calcul du terme suivant
        u=v # u_k devient u_(k+1)
        v=w # Et u_(k  +1) devient u_(k+2)
    return w  # ou v
    
# En testant cette fonction avec a=b=alpha=beta=1, on retombe sur la suite de Fibonacci. la rapport u_(n+1)/u_n tend vers le nombre d'or (1+sqrt(5))/2    

def recudouble_liste(n,a,b,alpha,beta):
    """ Cette fonction calcule et renvoie la liste [u_0,u_1,...,u_n] pour la suite définie par récurrence, u_0=alpha, u_1=beta et u_(n+2)=au_(n+1)+bu_n  """
    u=alpha
    v=beta # initialisation
    L=[u,v] # initialisation de la liste
    for k in range(2,n+1):
        w=a*v+b*u # Calcul du terme suivant
        u=v # u_k devient u_(k+1)
        v=w # Et u_(k  +1) devient u_(k+2)
        L.append(w) # on rajoute à la liste le terme que l'on vient de calculer
    return L


            

    


## Exercices 5-6-7-8-9-10 Sommes et produit

def sommeimpair(n):
    """ Cette fonction calcule et renvoie la somme des entiers impairs entre 1 et 2*n+1"""
    s=0 # initialisation
    for k in range(n+1):
        s+=2*k+1
    return s
# Tester cette fonction pour quelques valeurs de n, on conjecture que cette somme est égale à (n+1)**2, vous pouvez le retrouver par le calcul (linéarité de gros sigma)


def sommegeo(n,x):
    """ Cette fonction calcule et renvoie la somme des x**k pour k allant de 0 à n"""
    s=0 # initialisation
    for k in range(n+1): 
        s+=x**k
    return s
# Tester cette fonction pour quelques valeurs de n, et de x. On sait que cette somme est égale à (1-x**(n+1)/(1-x). Du coup si |x|<1, on voit que cette somme converge vers 1/(1-x).


def factorielle(n):
    """ Cette fonction calcule et renvoie la valeur de n! """
    f=1  # initialisation
    for k in range(1,n+1):
        f*=k # on remplace f par k*f à chaque étape
    return f
    
def coeffbinome(n,k):
    """ Cette fonction calcule et renvoie la valeur de k parmi n = n!/(k!*(n-k)!) """
    return int(factorielle(n)/(factorielle(k)*factorielle(n-k))) # le résultat étant entier, autant typer ce dernier, en plus si p>n, on renverra 0 au lieu d'un nombre réel plus petit que 1 (testez coeffbinome(4,6) avec ou sans int dans le return )
    
def coeffbinomebis(n,k): 
    """ Cette fonction calcule et renvoie la valeur de k parmi n = n!/(k!*(n-k)!), mais en simplifiant le plus gros des deux factorielles """
    if k>n:   # Si k est plus grand que n, k parmi n vaut 0
        return 0
    k=min(k,n-k)  # comme k parmi n est égal à n-k parmi n, on peut changer k en n-k
    # On va alors simplifier par (n-k)!, donc n! = (n*(n-1)*...*(n-k+1))/(1*2*...*k)
    c=1 #initialisation
    for j in range(k):
        c=c*(n-j)/(j+1)
    return int(c)



def sommebinome(a,b,n):
    """ Cette fonction calcule et renvoie la somme des k parmin n a**k*b/**(n-k) pour k allant de 0 à n"""
    s=0 # initialisation
    for k in range(n+1):
        s+=coeffbinomebis(n,k)*a**k*b**(n-k)  # on utilise la fonction définie ci-dessus (il faut l'avoir complilée !
    return s
# Tester cette fonction pour quelques valeurs de n,a,b, on conjecture que cette somme est égale à (a+b)**n, c'est la formule du binome !


def doubleproduit(n):
    """ Cette fonction calcule et renvoie le double produit des i*j  pour i,j allant de 1 à n"""
    p=1 # initialisation
    for i in range(1,n+1):
        for j in range(1,n+1):  # double produit donc double boucle pour
            p*=i*j
    return p
# Tester cette fonction pour quelques valeurs de n, on conjecture que ce produit est égal à ... (n!)**(2*n). C'est difficile de le conjecturer, il vaut mieux passer par le calcul.


## Exercice 11 - Suite de Syracuse
def recu(n,a):
    """ cette fonction calcule le nième terme de la suite de Syracuse avec u_0=a"""
    u=a # initialisation
    for k in range(n):
        if u%2==0: # si u est pair
            u=u/2 # on prend la moitié
        else:
            u=3*u+1 # on applique la définition de la suite.
    return int(u) # on renvoie un entier
    
def reculiste(n,a):
    """ Même fonction que ci-dessus, mais l'on stocke toutes les valeurs de la suite dans une liste """
    L=[a] # initiaisation de la liste
    for k in range(n-1):
        if L[k]%2==0: # si le terme en cours de la suite est pair
            L.append(int(L[k]/2)) # le terme suivant est égal à la moitié, on prend un entier. La méthode append permet d'agrandir la liste
        else:
            L.append(3*L[k]+1) # on applique la définition de la suite.
    return L # on renvoie la liste
    
def syracuse(a):
    """ Cette fonction calcule et renvoie le premier entier n tel que u_n = 1 """
    u=a # initialisation de la suite de Syracuse
    n=0 # C'est le terme d'indice 0
    L=[u] # on va construire la liste des termes de la suite
    while u!=1: # Tant que u_n est différent de 1, on calcule le terme suivant
        if u%2==0: # si u est pair
            u=u/2 # on prend la moitié
        else:
            u=3*u+1 # on applique la définition de la suite.
        L.append(int(u)) # on rajoute le nouveau terme à la liste
        n+=1 # Le rang augmente d'une unité
    print(L) # Affichage de la liste
    return n


def maxisyracuse(a):
    """ Cette fonction calcule et renvoie la valeur maximale atteinte par la suite de Syracuse initialisée par u0 = a """
    u=a # initialisation de la suite de Syracuse
    # n=0 # C'est le terme d'indice 0
    maxi=u # premier maximum provisoire, u_0
    while u!=1: # Tant que un est différent de 1, on calcule le terme suivant
        if u%2==0: # si u est pair
            u=u/2 # on prend la moitié
        else:
            u=3*u+1 # on applique la définition de la suite.
        if u>maxi:
            maxi=u # nouveau maximum provisoire
        # n+=1 # Le rang augmente d'une unité
    return int(maxi)
    
def longueursyracuse(n):
    """ Cette fonction calcule et renvoie l'entier a<=n pour lequel la suite de Syracuse compte le plus grand nombre de termes avant 1"""
    m=1 # initialisation
    longsyra=1 # initialisation
    for k in range(2,n+1):  # on va tester les entiers de 2 à n
        syra_k=syracuse(k)  # on calcule le nombre de termes de la suite de Syracuse
        if m<syra_k:  # Si ce nombre est plus grand que lemaximum provisoire
            m=syra_k  # on change ce maximum provisoire
            longsyra=k # k est le candidat provisoire
    return longsyra


