## Imports


from numpy import *
from random import *


## Listes aléatoires

def listealeatoire(a,b,n):
    """ Cette fonction renvoie une liste aléatoire de n entiers entra a et b """
    return([randint(a,b) for k in range(n)])


def codage(x):
    """ Cette fonction remplace un nombre x par l'un des 4 nucléotides A,C,G,T """
    if x==0:
        return "A"
    elif x==1:
        return "C"
    elif x==2:
        return "G"
    elif x==3:
        return "T"
    else:
        print("Erreur de codage")


def chaineADNaleatoire(n):
    """ Cette fonction renvoie une chaîne aléatoire de n codons d'ADN """
    L="" # initialisation d'une chaîne de caractère vide
    for k in range(n): # n codons d'ADN
        codon="" # initialisation d'un codon
        for i in range(3): # un codon comporte 3 nucléotide
            codon+=codage(randint(0,3))  # on tire au sort un nombre entre 0 et 3 et on le code
        L+=codon # on rajoute le codon généré à la chaîne    
    return L



## Exercices 1-2-3-4 - Recherche d'un élément donné à l'intérieur d'une liste
def appartenance(L,x):
    """ Cette fonction recherche un élément x dans une liste L. Elle renvoie True si x appartient à L, False sinon. Dans cette version, on itère sur les indices. """
    n=len(L) # taille de la liste
    for k in range(n):  # on parcourt la liste L
        if L[k]==x:    # Si l'élément d'indice k de L est égal à x
            return True  # Trouvé 
    return False # si l'on a parcouru la boucle sans rencontrer x, c'est qu'il n'appartient pas à L

def appartenance_bis(L,x):
    """ Cette fonction recherche un élément x dans une liste L. Elle renvoie True si x appartient à L, False sinon. Dans cette version, on itère directement sur les éléments de la liste. """
    n=len(L) # taille de la liste
    for z in L:  # on parcourt la liste L
        if z==x:    # Si l'élément z de L est égal à x
            return True  # Trouvé 
    return False # si l'on a parcouru la boucle sans rencontrer x, c'est qu'il n'appartient pas à L

def occurence(L,x):
    """ Cette fonction recherche un élément x dans une liste L. Elle renvoie le nombre d'occurences de x dans L.  """
    n=len(L) # taille de la liste
    occurence=0 # initialisation du compteur
    for k in range(n):  # on parcourt la liste L
        if L[k]==x:    # Si l'élément d'indice k de L est égal à x
            occurence+=1  # on augmente le compteur d'une unité
    return occurence
    
def occurence_bis(L,x):
    """ Cette fonction recherche un élément x dans une liste L. Elle renvoie le nombre d'occurences de x dans L. Dans cette version, on itère directement sur les éléments de la liste. """
    n=len(L) # taille de la liste
    occurence=0 # initialisation du compteur
    for z in L:  # on parcourt la liste L
        if z==x:    # Si l'élément z de L est égal à x
            occurence+=1  # on augmente le compteur d'une unité
    return occurence    
    
    

  
  
def indiceelement(L,x):
    """ Cette fonction recherche un élément x dans une liste L. Elle renvoie le premier indice de x dans L, False si x n'est pas dans L """
    n=len(L) # taille de la liste
    k=0 # initialisation du compteur
    while k<n:  # on parcourt la liste L
        if L[k]==x:    # Si l'élément d'indice k de L est égal à x
            return k  # on retourne l'indice
        k+=1
    print("L'élément x n'est pas dans la liste L")
    return None          # Si l'on arrive ici, c'est que l'on a parcouru la liste sans rencontrer x  
    
    
def Listeposition(L,x):
    """ Cette fonction renvoie la liste (éventuellement vide) de toutes les positions de l’élément x dans une liste donnée L """
    n=len(L) # taille de la liste
    P=[] # initialisation de la liste des positions
    for k in range(n):  # on parcourt la liste L
        if L[k]==x:    # Si l'élément d'indice k de L est égal à x
            P.append(k)  # on rajoute l'indice à la liste des positions
    return P    
    



    
## Exercices 5-6 - Maxima

def maximum(L):
    """ Cette fonction renvoie l'élément maximum (maxi) d’une liste L. Si la liste est vide, elle renvoie None."""
    n=len(L)   # taille de la chaîne
    if n==0: # liste vide
        return None
    maxi=L[0]  # premier maximum provisoire
    for k in range(1,n):
        if L[k]>maxi: # Si l'on rencontre un élément plus grand que le maximum provisoire
            maxi=L[k]  # On a un nouveau maximum provisoire
    return maxi


def Indice_max(L):
    """ Cette fonction recherche l'élément maximum (maxi) d’une liste L ainsi que sa place (maxind) dans la liste. Cette fonction renverra maxi et maxind."""
    n=len(L)   # taille de la chaîne
    maxi=L[0]  # premier maximum provisoire
    maxind=0  # Indice correspondant
    for k in range(1,n):
        if L[k]>maxi: # Si l'on rencontre un élément plus grand que le maximum provisoire
            maxi=L[k]  # On a un nouveau maximum provisoire
            maxind=k
    return maxi,maxind
    
def Position_max(L):
    """ Cette fonction renvoie la liste des positions du maximum dans L en utilisant la fonction Indice_max (2 parcours de L"""
    P=[]
    M=maximum(L)
    n=len(L)
    for k in range(n):
        if L[k]==M:
            P.append(k)
    return P
    
def Positions_max_opt(L):
    """ Cette fonction renvoie la liste des positions du maximum dans une liste L avec 1 seul parcours de L """
    n=len(L)   # taille de la chaîne
    maxi=L[0]  # premier maximum provisoire
    maxind=0  # Indice correspondant
    P=[maxind]  # Initialisation de liste de position
    for k in range(1,n):
        if L[k]>maxi: # Si l'on rencontre un élément plus grand que le maximum provisoire
            maxi=L[k]  # On a un nouveau maximum provisoire
            maxind=k
            P=[maxind] # On repart sur une nouvelle liste
        elif L[k]==maxi:
            P.append(k) # nouvelle position de l'éventuel maximum
    return P


def minimum(L):
    """ Cette fonction renvoie l'élément minimum (mini) d’une liste L. Si la liste est vide, elle renvoie None."""
    n=len(L)   # taille de la chaîne
    if n==0: # liste vide
        return None
    mini=L[0]  # premier minimum provisoire
    for k in range(1,n):
        if L[k]<mini: # Si l'on rencontre un élément plus grand que le maximum provisoire
            mini=L[k]  # On a un nouveau maximum provisoire
    return mini

# Que donne -maximum([-x for x in L]) ?


## Exercices 7-8-9-10  Second maximum

def min_max(L):
    """ Cette fonction renvoie le minimum et le maximum d'une liste L"""
    if L==[]:
        return None
    else:
        m=L[0] # premier minimum provisoire
        M=L[0] # premier maximum provisoire
        for i in range(1,len(L)):
            if L[i]<m:
                m=L[i] # nouveau minimum provisoire
            elif L[i]>M:
                M=L[i] # nouveau maximum provisoire
        return m,M

def secondmax(L):
    """ Cette fonction renvoie le second maximum de L """
    m,M=min_max(L) # on utilise la fonction préédente
    SM=m           # premier "second" maximum provisoire
    for i in range(1,len(L)):
        if SM<L[i]<M:
            SM=L[i]  # nouveau "second" maximum provisoire
    return SM

def secondmaxbis(L):
    """ Cette fonction renvoie le second maximum de L en ne parcourant qu'une seule fois la liste L. On suppose que L[0]!=L[1]"""
    if L[0]<L[1]:
        SM,M=L[0],L[1] # maximum (M) et "second" maximum provisoire (SM)
    else:
        SM,M=L[1],L[0]
    for k in range(2,len(L)):
        if L[k]>M: # si l'on trouve un élément plus grand
            M=L[k] # nouveau maximum provisoire
            SM=M   # Le nouveau "second" max est l'ancien max   
        elif M>L[k]>SM: # si on trouve un élément intercalé
            SM=L[k]   # nouveau "second" maximum provisoire
    return SM
    

def Indice_second_max(L):
    """ Cette fonction renvoie une des positions
du second maximum en ne parcourant L qu’une seule fois. On suppose que L[0]!=L[1]"""
    if L[0]<L[1]:
        SM,M=L[0],L[1] # maximum (M) et "second" maximum provisoire (SM)
        indm=1  # indice du max provisoire
        indsm=0 # indice du "second" max provisoire
    else:
        SM,M=L[1],L[0]
        indm=0
        indsm=1
    for k in range(2,len(L)):
        if L[k]>M: # si l'on trouve un élément plus grand
            M=L[k] # nouveau maximum provisoire
            SM=M   # Le nouveau "second" max est l'ancien max
            indsm=indm #Modification des indices
            indm=k   
        elif M>L[k]>SM: # si on trouve un élément intercalé
            SM=L[k]   # nouveau "second" maximum provisoire
            indsm=k
    return indsm
    
## Exercices 11-12-13 Approfondissement  

def deux_plus_proches_valeurs(L):
    """ Cette fonction renvoie les deux valeurs les plus proches d'une liste donnée L (contenant au moins deux valeurs)"""
    m=abs(L[1]-L[0]) # initialisation
    val_1=L[0]
    val_2=L[1]
    n=len(L)
    
    for i in range(n):
        for j in range(i+1,n):
            if abs(L[i]-L[j])<m:
                m=abs(L[i]-L[j])
                val_1=L[i]
                val_2=L[j]
    return [val_1,val_2]
    
def recherche_motif_1(mot,texte) :
    """ Cette fonction recherche la présence de la chaîne de caractère mot à l'intérieur de la chaîne texte. Elle renvoie True si mot est présent dans texte, False sino. Elle fonctionne aussi bien sur les listes de nombres que sur les chaînes de caractères. """
    m=len(mot) # taille de la chaîne mot
    n=len(texte) # taille de la chaîne texte
    presence=0 # initialisation du compteur
    for k in range(n-m+1): # il faut laisser la place pour mot, la première lettre ne peut donc être après le rang n-m
        if texte[k:k+m]==mot:  # on compare la sous-chaîne de texte de taille m commençant à l'indice k avec mot
            return True
    return False  
            
            
def recherche_motif_2(mot,texte) :
    """ Cette fonction renvoie la même chose que la précédente, mais sans utiliser de slice """
    m=len(mot) # taille de la chaîne mot
    n=len(texte) # taille de la chaîne texte
    presence=0 # initialisation du compteur
    for k in range(n-m+1):
        c=0 # compteur
        while c<m and texte[k+c]==mot[c]: # Tant que les lettres de texte et de mot coïncident
            c+=1  # on augmente le compteur
        if c==m:  # si le compteur est égal au nombre de lettres de mot, c'est qu'on l'a trouvé
            return True
    return False              