Aller au contenu

Exercices - Langages et programmation


Constructions élémentaires

Exercice 1 - Identifier les constructions

Pour chacun des extraits Python suivants, identifier les constructions élémentaires utilisées (affectation, séquence, conditionnelle, boucle bornée, boucle non bornée, appel de fonction).

a.

x = 10
y = x * 2
print(y)

b.

for i in range(5):
    if i % 2 == 0:
        print(i, "est pair")

c.

n = int(input("Nombre : "))
while n != 1:
    if n % 2 == 0:
        n = n // 2
    else:
        n = 3 * n + 1

Solution

a. Affectation (x = 10, y = x * 2), séquence (les 3 instructions dans l'ordre), appel de fonction (print(y)).

b. Boucle bornée (for i in range(5)), conditionnelle (if i % 2 == 0), appel de fonction (print(...)).

c. Appel de fonction (int(input(...))), affectation (n = ...), boucle non bornée (while n != 1), conditionnelle (if n % 2 == 0), affectation dans les branches.


Exercice 2 - Portée des variables

Prédire la sortie de chaque programme sans l'exécuter.

a.

x = 5

def modifier():
    x = 10
    print("Dans la fonction :", x)

modifier()
print("Après la fonction :", x)

b.

def f(a, b):
    a = a + b
    return a

x = 3
y = 7
z = f(x, y)
print(x, y, z)

Solution

a.

Dans la fonction : 10
Après la fonction : 5
Le x = 10 dans la fonction crée une variable locale x, distincte de la variable globale x. La variable globale x reste à 5.

b.

3 7 10
Le paramètre a reçoit une copie de x (3). a = a + b modifie la copie locale (a vaut 10), mais x reste à 3. La fonction renvoie 10, stocké dans z.


Exercice 3 - Comparer Python et JavaScript

Traduire le programme Python suivant en JavaScript :

def compter_voyelles(texte):
    voyelles = "aeiouyAEIOUY"
    compteur = 0
    for lettre in texte:
        if lettre in voyelles:
            compteur = compteur + 1
    return compteur

resultat = compter_voyelles("Bonjour le monde")
print(resultat)
Solution
function compterVoyelles(texte) {
    let voyelles = "aeiouyAEIOUY";
    let compteur = 0;
    for (let i = 0; i < texte.length; i++) {
        if (voyelles.includes(texte[i])) {
            compteur = compteur + 1;
        }
    }
    return compteur;
}

let resultat = compterVoyelles("Bonjour le monde");
console.log(resultat);

Points de comparaison : - deffunction, print()console.log() - Indentation → accolades { } - for lettre in texte:for (let i = 0; i < texte.length; i++) - in (appartenance) → .includes()


Spécification et tests

Exercice 4 - Spécifier une fonction

a. Écrire la spécification complète (prototype, docstring, préconditions, postconditions) de la fonction est_premier(n) qui teste si un entier est premier.

b. Implémenter la fonction.

c. Écrire un jeu d'au moins 6 tests couvrant des cas normaux, limites et particuliers.

Solution

a. et b.

def est_premier(n: int) -> bool:
    """Détermine si n est un nombre premier.

    Preconditions:
        n est un entier strictement positif.
    Postconditions:
        Renvoie True si n est premier (divisible uniquement par 1 et lui-même).
        Renvoie False sinon.
    """
    assert isinstance(n, int), "n doit être un entier"
    assert n > 0, "n doit être strictement positif"

    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0:
        return False
    d = 3
    while d * d <= n:
        if n % d == 0:
            return False
        d += 2
    return True

c.

# Cas limites
assert est_premier(1) == False, "1 n'est pas premier"
assert est_premier(2) == True, "2 est premier"
assert est_premier(3) == True, "3 est premier"

# Cas normaux
assert est_premier(7) == True, "7 est premier"
assert est_premier(10) == False, "10 n'est pas premier"
assert est_premier(13) == True, "13 est premier"

# Cas particuliers
assert est_premier(4) == False, "4 n'est pas premier"
assert est_premier(97) == True, "97 est premier"
assert est_premier(100) == False, "100 n'est pas premier"
print("Tous les tests passent !")


Exercice 5 - Trouver les erreurs

La fonction suivante est censée renvoyer le nombre d'occurrences d'un élément dans un tableau. Elle contient des erreurs. Les trouver et les corriger.

def compter(tableau, element)
    compteur = 0
    for i in range(len(tableau)):
        if tableau[i] = element:
            compteur + 1
    return compteur
Solution

Trois erreurs :

  1. Ligne 1 : il manque les : après la parenthèse → def compter(tableau, element):
  2. Ligne 4 : = est une affectation, il faut == pour la comparaison → if tableau[i] == element:
  3. Ligne 5 : compteur + 1 calcule mais ne stocke pas le résultat → compteur = compteur + 1 (ou compteur += 1)

Version corrigée :

def compter(tableau, element):
    compteur = 0
    for i in range(len(tableau)):
        if tableau[i] == element:
            compteur += 1
    return compteur


Exercice 6 - Tests avec doctest

Écrire la fonction pgcd(a, b) qui calcule le PGCD de deux entiers positifs par l'algorithme d'Euclide, avec des tests intégrés dans la docstring au format doctest.

Solution
def pgcd(a, b):
    """Calcule le PGCD de a et b par l'algorithme d'Euclide.

    Preconditions: a et b entiers >= 0, pas tous les deux nuls.

    >>> pgcd(12, 8)
    4
    >>> pgcd(7, 3)
    1
    >>> pgcd(100, 25)
    25
    >>> pgcd(17, 0)
    17
    >>> pgcd(0, 5)
    5
    >>> pgcd(48, 18)
    6
    """
    assert isinstance(a, int) and isinstance(b, int)
    assert a >= 0 and b >= 0
    assert not (a == 0 and b == 0)

    while b != 0:
        a, b = b, a % b
    return a

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Modules et mise au point

Exercice 7 - Utiliser des modules

a. En utilisant le module math, calculer : \(\sqrt{2}\), \(\lceil 7,3 \rceil\) (plafond), \(\lfloor 7,8 \rfloor\) (plancher), \(\log_2(256)\).

b. En utilisant le module random, écrire une fonction lancer_des(n) qui simule le lancer de n dés à 6 faces et renvoie la somme.

c. Écrire une fonction mot_de_passe(longueur) qui génère un mot de passe aléatoire de la longueur donnée, composé de lettres minuscules et de chiffres.

Solution

a.

import math

print(math.sqrt(2))      # 1.4142135623730951
print(math.ceil(7.3))    # 8
print(math.floor(7.8))   # 7
print(math.log2(256))    # 8.0

b.

import random

def lancer_des(n):
    """Simule n lancers de dé et renvoie la somme."""
    somme = 0
    for _ in range(n):
        somme += random.randint(1, 6)
    return somme

print(lancer_des(3))     # un nombre entre 3 et 18

c.

import random

def mot_de_passe(longueur):
    """Génère un mot de passe aléatoire."""
    caracteres = "abcdefghijklmnopqrstuvwxyz0123456789"
    mdp = ""
    for _ in range(longueur):
        mdp += random.choice(caracteres)
    return mdp

print(mot_de_passe(12))  # ex: "k7m2pq9xf4a1"


Exercice 8 - Créer un module

a. Créer un module geometrie.py contenant les fonctions :

  • aire_cercle(rayon) → renvoie l'aire du cercle (\(\pi r^2\))
  • perimetre_cercle(rayon) → renvoie le périmètre (\(2 \pi r\))
  • aire_triangle(base, hauteur) → renvoie l'aire (\(\frac{base \times hauteur}{2}\))

b. Ajouter des assertions de précondition (rayon, base et hauteur > 0).

c. Ajouter un bloc if __name__ == "__main__" avec des tests.

d. Utiliser ce module depuis un fichier programme.py.

Solution

geometrie.py :

import math

def aire_cercle(rayon):
    """Renvoie l'aire d'un cercle de rayon donné."""
    assert rayon > 0, "Le rayon doit être strictement positif"
    return math.pi * rayon ** 2

def perimetre_cercle(rayon):
    """Renvoie le périmètre d'un cercle de rayon donné."""
    assert rayon > 0, "Le rayon doit être strictement positif"
    return 2 * math.pi * rayon

def aire_triangle(base, hauteur):
    """Renvoie l'aire d'un triangle."""
    assert base > 0 and hauteur > 0, "Base et hauteur doivent être positives"
    return base * hauteur / 2

if __name__ == "__main__":
    assert abs(aire_cercle(1) - math.pi) < 0.001
    assert abs(perimetre_cercle(1) - 2 * math.pi) < 0.001
    assert aire_triangle(6, 4) == 12.0
    print("Tests OK !")

programme.py :

from geometrie import aire_cercle, aire_triangle

print(f"Aire d'un cercle de rayon 5 : {aire_cercle(5):.2f}")
print(f"Aire d'un triangle 10x6 : {aire_triangle(10, 6):.2f}")


Exercice 9 - Déboguer un programme

Le programme suivant est censé renvoyer la liste des nombres premiers inférieurs à n. Il contient une erreur de logique. La trouver en utilisant des affichages intermédiaires.

def premiers_jusqu_a(n):
    resultat = []
    for candidat in range(2, n):
        est_premier = True
        for diviseur in range(2, candidat):
            if candidat % diviseur == 0:
                est_premier = False
        if est_premier:
            resultat.append(candidat)
    return resultat

print(premiers_jusqu_a(20))

a. Exécuter le programme. Le résultat est-il correct ?

b. Si non, ajouter des print() pour identifier le problème et corriger.

Solution

a. Le programme fonctionne correctement et renvoie [2, 3, 5, 7, 11, 13, 17, 19].

Cependant, il est inefficace : même après avoir trouvé un diviseur, la boucle interne continue. On peut l'optimiser avec un break :

b.

def premiers_jusqu_a(n):
    resultat = []
    for candidat in range(2, n):
        est_premier = True
        for diviseur in range(2, candidat):
            if candidat % diviseur == 0:
                est_premier = False
                break    # inutile de continuer, on sait que c'est pas premier
        if est_premier:
            resultat.append(candidat)
    return resultat

On peut aussi optimiser en ne testant que jusqu'à \(\sqrt{candidat}\) :

import math

def premiers_jusqu_a(n):
    resultat = []
    for candidat in range(2, n):
        est_premier = True
        for diviseur in range(2, int(math.sqrt(candidat)) + 1):
            if candidat % diviseur == 0:
                est_premier = False
                break
        if est_premier:
            resultat.append(candidat)
    return resultat

Exercice 10 - Messages d'erreur

Pour chaque extrait de code, prédire le type d'erreur Python qui sera levée et expliquer pourquoi.

a. print(10 / 0)

b. int("abc")

c. [1, 2, 3][5]

d. "3" + 5

e. {"a": 1}["b"]

Solution

a. ZeroDivisionError : division par zéro, opération mathématiquement interdite.

b. ValueError : la chaîne "abc" ne peut pas être convertie en entier.

c. IndexError: list index out of range : l'indice 5 n'existe pas dans une liste de 3 éléments (indices 0, 1, 2).

d. TypeError: can only concatenate str to str : on ne peut pas additionner une chaîne et un entier. Il faudrait écrire "3" + str(5) ou int("3") + 5.

e. KeyError: 'b' : la clé "b" n'existe pas dans le dictionnaire. On peut utiliser .get("b", valeur_defaut) pour éviter l'erreur.



Activités


🧪 Activité 1 - Le même algorithme en plusieurs langages

Objectif

Comparer l'implémentation d'un même algorithme dans différents langages pour identifier les traits communs et les différences.

Étapes

a. Écrire une fonction qui calcule le maximum d'un tableau en Python, JavaScript et pseudo-code.

b. Pour chaque version, identifier : la déclaration de fonction, la boucle, la condition, le retour de valeur.

c. Quels sont les traits communs entre ces langages ? Quels sont les traits particuliers à chaque langage ?

Correction

Python :

def maximum(tableau):
    maxi = tableau[0]
    for element in tableau:
        if element > maxi:
            maxi = element
    return maxi

JavaScript :

function maximum(tableau) {
    let maxi = tableau[0];
    for (let i = 0; i < tableau.length; i++) {
        if (tableau[i] > maxi) {
            maxi = tableau[i];
        }
    }
    return maxi;
}

Traits communs : mot-clé return, variable maxi, comparaison >, structure if.

Différences : Python utilise for element in tableau (itération directe) et l'indentation, JavaScript utilise for (let i=0; ...) (itération par indice) et les accolades.


🧪 Activité 2 - Atelier TDD

Objectif

Pratiquer le développement piloté par les tests en écrivant les tests avant le code.

Étapes

On souhaite écrire une fonction cesar(texte, decalage) qui chiffre un texte par le chiffre de César (décalage de chaque lettre dans l'alphabet).

a. Écrire d'abord les tests :

assert cesar("ABC", 3) == "DEF"
assert cesar("XYZ", 3) == "ABC"
assert cesar("HELLO", 0) == "HELLO"
assert cesar("NSI", 13) == "AFV"
assert cesar("abc", 1) == "bcd"

b. Implémenter la fonction pour que tous les tests passent. Les caractères non alphabétiques restent inchangés.

c. Écrire la fonction de déchiffrement decesar(texte, decalage) et vérifier que decesar(cesar(texte, d), d) == texte.

Correction
def cesar(texte, decalage):
    """Chiffre un texte par le chiffre de César."""
    resultat = ""
    for c in texte:
        if "A" <= c <= "Z":
            resultat += chr((ord(c) - ord("A") + decalage) % 26 + ord("A"))
        elif "a" <= c <= "z":
            resultat += chr((ord(c) - ord("a") + decalage) % 26 + ord("a"))
        else:
            resultat += c
    return resultat

def decesar(texte, decalage):
    """Déchiffre un texte chiffré par César."""
    return cesar(texte, -decalage)

# Tests
assert cesar("ABC", 3) == "DEF"
assert cesar("XYZ", 3) == "ABC"
assert cesar("HELLO", 0) == "HELLO"
assert cesar("NSI", 13) == "AFV"
assert cesar("abc", 1) == "bcd"

# Test aller-retour
assert decesar(cesar("Bonjour le monde", 7), 7) == "Bonjour le monde"
print("Tous les tests passent !")

🎯 Projet final - Bibliothèque de fonctions utilitaires

Objectif

Créer un module Python complet, spécifié, testé et documenté.

Partie A - Créer le module

a. Créer un fichier utilitaires.py contenant les fonctions suivantes, chacune avec docstring, préconditions et postconditions :

  • inverser(texte) : renvoie le texte inversé
  • est_palindrome(texte) : vérifie si un texte est un palindrome (ignore la casse)
  • compter_mots(texte) : renvoie le nombre de mots dans un texte
  • frequence_lettres(texte) : renvoie un dictionnaire des fréquences de chaque lettre

Partie B - Tester

b. Écrire un jeu de tests complet pour chaque fonction (au moins 4 tests par fonction, incluant les cas limites).

c. Ajouter les tests dans la docstring au format doctest.

Partie C - Utiliser

d. Créer un fichier analyse.py qui importe le module et analyse un texte saisi par l'utilisateur : affiche le texte inversé, teste s'il est palindrome, compte les mots et affiche les 5 lettres les plus fréquentes.

Correction

utilitaires.py :

def inverser(texte):
    """Renvoie le texte inversé.

    >>> inverser("Bonjour")
    'ruojnoB'
    >>> inverser("")
    ''
    >>> inverser("a")
    'a'
    >>> inverser("abba")
    'abba'
    """
    return texte[::-1]

def est_palindrome(texte):
    """Vérifie si texte est un palindrome (insensible à la casse).

    >>> est_palindrome("kayak")
    True
    >>> est_palindrome("Kayak")
    True
    >>> est_palindrome("python")
    False
    >>> est_palindrome("")
    True
    >>> est_palindrome("a")
    True
    """
    texte_min = texte.lower()
    return texte_min == texte_min[::-1]

def compter_mots(texte):
    """Renvoie le nombre de mots dans le texte.

    >>> compter_mots("Bonjour le monde")
    3
    >>> compter_mots("")
    0
    >>> compter_mots("mot")
    1
    >>> compter_mots("  espaces   multiples  ")
    2
    """
    if texte.strip() == "":
        return 0
    return len(texte.split())

def frequence_lettres(texte):
    """Renvoie un dictionnaire des fréquences de chaque lettre (en minuscule).

    >>> frequence_lettres("aab")
    {'a': 2, 'b': 1}
    >>> frequence_lettres("")
    {}
    >>> frequence_lettres("123")
    {}
    """
    freq = {}
    for c in texte.lower():
        if c.isalpha():
            if c in freq:
                freq[c] += 1
            else:
                freq[c] = 1
    return freq

if __name__ == "__main__":
    import doctest
    doctest.testmod()
    print("Tous les tests passent !")

analyse.py :

from utilitaires import inverser, est_palindrome, compter_mots, frequence_lettres

texte = input("Entrez un texte : ")

print(f"\nTexte inversé : {inverser(texte)}")
print(f"Palindrome : {'Oui' if est_palindrome(texte) else 'Non'}")
print(f"Nombre de mots : {compter_mots(texte)}")

freq = frequence_lettres(texte)
top5 = sorted(freq.items(), key=lambda x: x[1], reverse=True)[:5]
print("Top 5 des lettres :")
for lettre, nombre in top5:
    print(f"  '{lettre}' : {nombre} fois")