#######################
##### Palindromes #####
#######################


# 1
def position(let:str) -> int :
    """ Version courte """
    return(ord(let.lower())-ord("a"))

def position2(let:str) -> int :
    # ord("a")-ord("A")=32. Et il n'y a que 26 lettres dans l'alphabet. Donc
    # (ord(let)-ord("A"))%32 correspond à l'écart entre let et la lettre a, sans tenir compte
    # de la casse (majuscule ou minuscule). 
    return((ord(let)-ord("A"))%32) 

############
# 2
def epureMot(mot:str) -> str :
    """Version courte"""
    Mot = ""
    for let in mot :
        if let.isalpha() :
            Mot += let.upper()
    return(Mot)

def epureMot2(mot:str) -> str :
    """Version complète"""
    Mot = ""
    for let in mot :
        if let.isalpha() : # si let est une lettre (donc pas un espace)
            Mot += chr(position(let)+65)
    return(Mot)
            

############
# 3
# Il y a plusieurs façons de faire. On peut lire le mot, lettre par lettre dans les deux sens
# et comparer chaque lettre.

# On peut également écrire le mot à l'envers et comparer les deux mots.

############
# 4
# Pour la version la plus courte et la plus simple : 
def palindrome(mot:str) -> bool :
    """ Version courte """
    return(mot == mot[::-1])
# Mais on ne voit pas bien le nombre de comparaison. En fait, toutes les lettres sont comparées.
# Or on peut s'arrêter seulement à la moitié du mot.

def palindrome2(mot:str) -> bool :
    """ Version complète """
    n = len(mot)
    for k in range(n//2) : # Si le nb de lettre est impair, la lettre médiane se correspond à elle-même. Donc inutile de la considérer.
        if mot[k] != mot[-k-1] :
            return(False)
    return(True)

##########
# 5
def retourne(n1:str) -> str :
    """ Version courte """
    return(n1[::-1])

def retourne2(n1:str) -> str :
    N = ""
    for k in n1 :
        N = k+N # Il suffit de mettre les nouvelles lettres au début et pas à la fin. 
    return(N)

##########
# 6
# Dans sa version la plus courte : 
def retournables(n1:str, n2:str) -> bool :
    """ Version courte """
    return(n1[::-1]==n2)
# Ça ne se voit pas, mais c'est un algorithme de type recherche. En effet, il y a un parcourt de listes et une comparaison
# des éléments effectués. 
 
def retournables2(n1:str, n2:str) -> bool :
    """ Version courte en utilisant la fonction retourne. """
    return(retourne(str(n1))==str(n2))

# En détaillant ce qu'il se passe :
def retournables3(n1:str, n2:str) -> bool :
    """ Version complète """
    N = len(n1)
    if len(n2)!=N : # Si pas la même longueur, inutile d'aller plus loin 
        return(False)
    else :
        for k in range(N) : # On compare les lettres les unes avec les autres 
            if n1[k]!=n2[-k-1] :
                return(False)
    return(True)
# C'est donc bien un algorithme de type recherche.

##########
# 7
def carreRetournable(n:int) -> bool :
    return(int(retourne(str(n)))**2==int(retourne(str(n**2))))
# Attention à la position des int et str : dans retourne(n**2), il peut y avoir des 0 au début.

##########
# 8
def carresRetournables(n:int) -> list :
    L = []
    for k in range(n+1) :
        if carreRetournable(k) :
            L += [k]
    return(L)

#########
# 9
def nombreERPN(n:int) -> bool :
    for k in range(n) :
        if int(retourne(str(k)))*k == n :
            return(True)
    return(False)

#########
# 10
def nombresERPN(n:int) -> list :
    L = []
    for k in range(n+1) :
        if nombreERPN(k) :
            L += [k]
    return(L)