Aller au contenu

Exercices d'entraînement

Ces exercices doivent être utilisés pour vous entraîner à programmer. Ils sont généralement accompagnés d'aide et de leur solution pour vous permettre de progresser.

Avant de vous précipiter sur ces solutions dès la première difficulté, n'oubliez pas les conseils suivants :

  • Avez-vous bien fait un schéma au brouillon pour visualiser le problème posé ?
  • Avez-vous essayé de rédiger un algorithme en français, avec vos propres mots, avant de vous lancer dans la programmation sur machine ?
  • Avez-vous utilisé des affichages intermédiaires, des print(), pour visualiser au fur et à mesure le contenu des variables ?
  • Avez-vous testé le programme avec les propositions de tests donnés dans l'exercice ?
  • Avez-vous testé le programme avec de nouveaux tests, différents de ceux proposés ?

Rappels

  • Chaque programme Python doit être sauvegardé sous forme de fichier texte avec l'extension .py.
    Enregistrez ce fichier dans le dossier [F03-Finaliser_Programmes] avec le nom donné à l'exercice : ProgF03.51.py, ProgF03.52.py, etc...
  • Pour exécuter ce programme, il suffit de le sauvegarder puis d'appuyer sur la touche [F5].

ProgF03.51

On rappelle ci-dessous la définition de la fonction somme_impair() déjà étudiée dans un des exemples du cours.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def somme_impair(n):
    """
    n - int, entier strictement positif
    Sortie: int - somme des n premiers entiers impairs
    >>> somme_impair(1)
    1
    >>> somme_impair(4)
    16
    """

    s = 0
    for i in range(n):
        s = s + 2*i+1
    return s

##----- Mise en oeuvre des tests -----##
if __name__ == "__main__":
    import doctest
    doctest.testmod()

Ajoutez une assertion en ligne 10 afin de renforcer les spécifications de cette fonction.

Une réponse

Il faut vérifier que le paramètre est bien un entier stictement positif. Il faut donc vérifier deux conditions :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def somme_impair(n):
    """
    n - int, entier strictement positif
    Sortie: int - somme des n premiers entiers impairs
    >>> somme_impair(1)
    1
    >>> somme_impair(4)
    16
    """
    assert isinstance(n, int) and n>0, "Le paramètre doit être un entier strictement positif"
    s = 0
    for i in range(n):
        s = s + 2*i+1
    return s

##----- Mise en oeuvre des tests -----##
if __name__ == "__main__":
    import doctest
    doctest.testmod()

ProgF03.52

La fonction meme_signe(a, b) doit renvoyer True lorsque a et b sont des entiers de même signe et False dans le cas contraire. On considère que 0 est positif et négatif.

  1. Complétez le corps de cette fonction en respectant ses spécifications.
    En ligne 6, remplacez l'instruction pass par une assertion pour vérifier que les paramètres saisis sont bien des entiers.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    def meme_signe(a, b):
        """
        a, b - int
        Sortie: bool - True si a et b sont de même signe, False sinon
        """
        pass
    
    ##----- Mise en oeuvre des tests -----##
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    
    Une solution
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    def meme_signe(a, b):
        """
        a, b - int
        Sortie: bool - True si a et b sont de même signe, False sinon
        """
        assert isinstance(a, int) and isinstance(b, int), "Les paramètres doivent être entiers"
    
        if a*b >= 0:
            return True
        else:
            return False
    
    ##----- Mise en oeuvre des tests -----##
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    
  2. Utilisez le plan de test réalisé dans le cours afin de compléter le docstring avec ces tests.

    Une solution
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    def meme_signe(a, b):
        """
        a, b - int
        Sortie: bool - True si a et b sont de même signe, False sinon
        >>> meme_signe(2, 4)
        True
        >>> meme_signe(-2, 4)
        False
        >>> meme_signe(6, -5)
        False
        >>> meme_signe(-4, -2)
        True
        >>> meme_signe(0, -3)
        True
        """
        assert isinstance(a, int) and isinstance(b, int), "Les paramètres doivent être entiers"
    
        if a*b >= 0:
            return True
        else:
            return False
    
    ##----- Mise en oeuvre des tests -----##
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    
  3. Testez votre programme en l'exécutant et vérifier que les tests ne renvoient aucune erreur.

ProgF03.53 - Anagramme

Définition

Un mot est une anagramme d'un autre mot s'ils ont exactement les mêmes lettres.

Exemples

  • « aimer » et « marie » ;
  • « marion » et « manoir » ;
  • « casser » et « ressac ».

Par contre « miam » et « iam » ne sont pas des anagrammes : les lettres sont les mêmes mais pas avec le même effectif.

La fonction sont_anagrammes() prend en paramètres deux chaînes de caractères mot1 et mot2 puis renvoie True si ces deux mots sont anagrammes l'un de l'autre et False sinon.

On propose le code suivant pour cette fonction :

1
2
3
4
5
6
7
8
9
def sont_anagrammes(mot1, mot2):
    """
    mot1, mot2 - str
    Sortie: bool - True si mot1 et mot2 sont anagrammes, False sinon
    """
    for lettre in mot1:
        if lettre not in mot2:
            return False
    return True
  1. Effectuez les tests suivants :

    >>> sont_anagrammes('aimer','marie')
    
    >>> sont_anagrammes('aha','ubu')
    
    Semblent-ils signaler que la fonction est correcte ?

    Une solution

    On obtient les réponses suivantes, ce qui pourrait faire croire que la programmation est correcte :

    >>> sont_anagrammes('aimer', 'marie')
    True
    
    >>> sont_anagrammes('aha', 'ubu')
    False
    

  2. Montrez que ce code ne convient pas en explicitant un test adéquat.

    Un test possible

    >>> sont_anagrammes('aimer', 'marier')
    True
    
    Le test renvoie True alors que 'marier' contient une lettre 'r' de trop.

  3. Expliquez le problème.

    Explication

    Le code vérifie que chaque lettre du premier mot est présente dans le second mot... mais ne vérifie pas si elle est présente avec un plus grand nombre d'occurrences.

ProgF03.54 - Anagramme (bis)

Pour corriger le problème souligné à l'exercice précédent, on propose le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def compte(mot, lettre):
    """
    mot - str, une chaîne de caractères
    lettre - str, un seul caractère
    Sortie: int, le nombre d'occurrences de lettre dans mot
    """
    compteur = 0
    for carac in mot:
        if carac == lettre:
            compteur += 1
    return compteur


def sont_anagrammes(mot1, mot2):
    """
    mot1, mot2 - str
    Sortie: bool - True si mot1 et mot2 sont anagrammes, False sinon
    """
    for lettre in mot1:
        if compte(mot1, lettre) != compte(mot2, lettre):
            return False
    return True
  1. Effectuez les tests suivants :

    >>> sont_anagrammes('aimer','marie')
    
    >>> sont_anagrammes('aimer','marier')
    
    >>> sont_anagrammes('aha','ubu')
    
    Semblent-ils signaler que la fonction est correcte ?

    Une solution

    On obtient les réponses suivantes, ce qui pourrait faire croire que la programmation est correcte :

    >>> sont_anagrammes('aimer', 'marie')
    True
    
    >>> sont_anagrammes('aimer', 'marier')
    False
    
    >>> sont_anagrammes('aha', 'ubu')
    False
    

  2. Montrez que ce code ne convient pas en explicitant un test adéquat.

    Un test possible

    >>> sont_anagrammes('aimer', 'marie-b')
    True
    
    Le code ne vérifie pas si mot2 contient d'autres lettres que celles de mot1.

  3. Proposez une correction de ce code.

    Une solution
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    def compte(mot, lettre):
        """
        mot - str, une chaîne de caractères
        lettre - str, un seul caractère
        Sortie: int, le nombre d'occurrences de lettre dans mot
        """
        compteur = 0
        for carac in mot:
            if carac == lettre:
                compteur += 1
        return compteur
    
    
    def sont_anagrammes(mot1, mot2):
        """
        mot1, mot2 - str
        Sortie: bool - True si mot1 et mot2 sont anagrammes, False sinon
        """
        for lettre in mot1:
            if compte(mot1, lettre) != compte(mot2, lettre):
                return False
        for lettre in mot2:
            if compte(mot1, lettre) != compte(mot2, lettre):
                return False
        return True
    

ProgF03.55* - Anagramme (ter)

Dans la solution de l'exercice précédent, le code de la fonction sont_anagrammes() présente deux blocs de structures identiques :

  • le bloc for lettre in mot1: ...
  • et le bloc for lettre in mot2: ...

Ce type de répétition dans le code semble naturel pour un « premier jet » mais il faudra apprendre à l'éviter.

Dès qu'un code est répété, créez une fonction intermédiaire évitant la répétition.

Proposez une réécriture du code précédent permettant d'éviter cette répétition de boucle.

Une solution

La fonction est_composable() ci-dessous ne reprend pas tout à fait le code répété : on a remplacé l'égalité par une inégalité. A vous de comprendre pourquoi cela suffit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def compte(mot, lettre):
    """
    mot - str, une chaîne de caractères
    lettre - str, un seul caractère
    Sortie: int, le nombre d'occurrences de lettre dans mot
    """
    compteur = 0
    for carac in mot:
        if carac == lettre:
            compteur += 1
    return compteur


def est_composable(mot, jeu):
    """
    mot - str
    jeu - str, jeu est un ensemble de lettres (que l'on peut voir comme 
    le jeu de lettres dont on dispose au scrabble au moment où l'on doit jouer)
    Sortie: bool - True si on peut écrire mot avec les lettres disponibles dans jeu,
            False sinon.
    >>> est_composable('aha', 'ahbc')
    False
    >>> est_composable('aha', 'ahbac')
    True
    >>> est_composable('aha', 'ahbaa')
    True
    """
    for lettre in mot:
        if compte(mot, lettre) > compte(jeu, lettre):
            return False
    return True


def sont_anagrammes(mot1, mot2):
    """
    mot1, mot2 - str
    Sortie: bool - True si mot1 et mot2 sont anagrammes, False sinon
    """
    return est_composable(mot1, mot2) and est_composable(mot2, mot1)

ProgF03.56 - Affichages intermédiaires

Définir la fonction maxi(n) qui génère successivement des entiers aléatoires entre 0 et n et renvoie le plus grand des entiers ainsi générés. La boucle s'arrête lorsque l'entier généré est 0.

Rappel sur le module random

Le module random contient des fonctions qui permettent de générer des nombres pseudo-aléatoires. Parmi ces fonctions, on trouve :

  • randint(a, b) génère un entier compris entre a (inclus) et b (inclus).
  • random() génère un flottant compris entre 0 (inclus) et 1 (exclus).

On peut importer toutes les fonctions incluses dans le module random en début de programme grâce à l'instruction : from random import *.

Une piste : une spécification possible
1
2
3
4
5
6
7
8
from random import randint

def maxi(n):
    """
    n – int, entier strictement positif
    Sortie: int – le plus grand des entiers aléatoires générés entre 0 et n.
            On renvoie cet entier dès que 0 est généré.
    """
Une autre piste

Effectuez des affichages intermédiaires pour vérifier les valeurs aléatoires générées.

Une solution
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from random import randint

def maxi(n):
    """
    n – int, entier strictement positif
    Sortie: int – le plus grand des entiers aléatoires générés entre 0 et n.
            On renvoie cet entier dès que 0 est généré.
    """
    maximum = 0
    nb_obtenu = randint(0, n)
    while nb_obtenu != 0:
        if nb_obtenu > maximum:
            maximum = nb_obtenu
        nb_obtenu = randint(0, n)
    return maximum        

On peut ajouter un affichage en ligne 12 pour vérifier les valeurs du nombre généré et du maximum mis en mémoire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from random import randint

def maxi(n):
    """
    n – int, entier strictement positif
    Sortie: int – le plus grand des entiers aléatoires générés entre 0 et n.
            On renvoie cet entier dès que 0 est généré.
    """
    maximum = 0
    nb_obtenu = randint(0, n)
    while nb_obtenu != 0:
        print(f"nombre obtenu : {nb_obtenu}, maximum : {maximum}")
        if nb_obtenu > maximum:
            maximum = nb_obtenu
        nb_obtenu = randint(0, n)
    return maximum
Voici un exemple d'appel :
>>> maxi(10)
nombre obtenu : 8, maximum : 0
nombre obtenu : 1, maximum : 8
nombre obtenu : 4, maximum : 8
nombre obtenu : 9, maximum : 8
nombre obtenu : 1, maximum : 9
nombre obtenu : 9, maximum : 9
nombre obtenu : 6, maximum : 9
nombre obtenu : 5, maximum : 9
nombre obtenu : 3, maximum : 9
nombre obtenu : 9, maximum : 9
nombre obtenu : 6, maximum : 9
nombre obtenu : 4, maximum : 9
9