Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 924 minDébutant

Chaînes de caractères et code de César

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • extraire, chercher, découper et assembler des chaînes ;
  • formater un affichage et parcourir une chaîne caractère par caractère ;
  • passer d'un caractère à son code avec ord et chr ;
  • programmer le code de César et le chiffrement affine.

6 exercices corrigés1 schéma

Une chaîne de caractères est une suite de caractères (lettres, chiffres, symboles, espaces) traitée comme une seule valeur, par exemple un nom, une adresse IP ou un message. Ce chapitre présente les opérations du programme sur les chaînes (concaténation, extraction au début, au milieu ou à la fin, recherche d'un motif), leur traduction en Python, les algorithmes classiques sur les mots, puis deux chiffrements : le code de César et le chiffrement affine.

Écrire une chaîne

En Python, une chaîne s'écrit entre guillemets doubles ou simples : "BTS SIO" ou 'BTS SIO'. On choisit les doubles quand le texte contient une apostrophe : "l'adresse IP". Quelques caractères spéciaux s'écrivent avec une barre oblique inverse :

ÉcritureCaractère
\nRetour à la ligne
\tTabulation
\" ou \'Guillemet dans une chaîne délimitée par le même guillemet
\\Barre oblique inverse (chemins Windows : "C:\\Temp")

La chaîne vide "" ne contient aucun caractère ; sa longueur est 0. Elle sert à initialiser une chaîne qu'on construit dans une boucle, comme 0 initialise une somme.

Les opérations de base

OpérationPseudo-codePythonExempleRésultat
Longueurlongueur(s)len(s)len("réseau")6
Concaténations1 & s2 ou s1 + s2s1 + s2"web" + "01""web01"
Répétitions * n"-" * 5"-----"
Caractère d'indice is[i]s[i]"SIO"[0]"S"
Comparaison=, <==, <"abc" < "abd"True

La concaténation n'accepte que des chaînes : "Poste " + 12 lève TypeError. Il faut convertir : "Poste " + str(12).

Les caractères sont numérotés à partir de 0, comme les éléments d'une liste. Mais une chaîne Python est non modifiable : s[0] = "X" lève TypeError: 'str' object does not support item assignment. Pour « modifier » une chaîne, on en construit une nouvelle. C'est l'une des raisons pour lesquelles le programme recommande de considérer la chaîne comme un type à part entière, plutôt que comme un tableau de caractères.

Extraire le début, le milieu ou la fin

Le programme demande les fonctions d'extraction « en début, milieu ou fin ». En pseudo-code, on les écrit souvent Gauche, Droite et Milieu (comme dans un tableur) ; en Python, on utilise le découpage (slicing) s[début:fin], qui prend les caractères de l'indice début inclus à fin exclu, comme range.

BesoinPseudo-codePythonAvec s = "Réseaux et systèmes"
Les n premiers caractèresGauche(s, n)s[:n]s[:6] donne "Réseau"
Les n derniersDroite(s, n)s[-n:]s[-7:] donne "ystèmes"
n caractères à partir de l'indice dMilieu(s, d, n)s[d:d + n]s[3:6] donne "eau"
Tout sauf le premiers[1:]"éseaux et systèmes"
La chaîne à l'enverss[::-1]"semètsys te xuaeséR"

Un découpage ne lève jamais d'erreur d'indice : s[:100] renvoie toute la chaîne si elle est plus courte. Accéder à un caractère seul, en revanche (s[100]), lève IndexError.

Chercher un motif

BesoinPythonExempleRésultat
Le motif est-il présent ?motif in s"@" in "j.dupont@lycee.fr"True
Position de la première occurrences.find(motif)"admin;user".find(";")5 (−1 si absent)
Nombre d'occurrencess.count(motif)"192.168.1.1".count(".")3
Commence par, finit pars.startswith(m), s.endswith(m)"rapport.pdf".endswith(".pdf")True
Remplacers.replace(ancien, nouveau)"a-b-c".replace("-", ":")"a:b:c"

find renvoie −1 quand le motif est absent, comme la recherche séquentielle du cours. La méthode voisine index lève une ValueError à la place.

Transformer et tester

BesoinPythonExempleRésultat
Majuscules, minusculess.upper(), s.lower()"Sio".upper()"SIO"
Retirer les espaces au bords.strip()" admin \n".strip()"admin"
Que des chiffres ?s.isdigit()"2026".isdigit()True
Que des lettres ?s.isalpha()"Léa".isalpha()True
Majuscule, minuscule ?c.isupper(), c.islower()"A".isupper()True

Ces méthodes renvoient une nouvelle chaîne et ne modifient pas s. Écrire s.upper() seul, sans affecter le résultat, ne sert à rien : il faut s = s.upper().

Découper et assembler

split découpe une chaîne selon un séparateur et renvoie une liste de morceaux ; join fait l'inverse.

python
ip = "192.168.10.25"
octets = ip.split(".")
print(octets)
print(int(octets[3]) + 1)

ligne = "jdupont;Dupont;Jean;Comptabilité"
champs = ligne.split(";")
print(champs[1], champs[3])

print(" ".join(["Bonjour", "le", "BTS"]))
print("-".join(octets))
text
['192', '168', '10', '25']
26
Dupont Comptabilité
Bonjour le BTS
192-168-10-25

Sans argument, split() découpe sur les espaces (un ou plusieurs) : " trois mots ici ".split() donne ["trois", "mots", "ici"]. Les morceaux sont des chaînes : convertis-les si ce sont des nombres.

Formater un affichage

Construire un message, c'est assembler du texte et des valeurs. Trois façons, de la plus simple à la plus pratique :

python
nom = "Léa"
moyenne = 15.756
rang = 2

print("Élève :", nom, "- moyenne :", moyenne)              # print avec virgules
print("Élève : " + nom + " - rang : " + str(rang))         # concaténation
print(f"Élève : {nom} - moyenne : {moyenne:.2f} - rang : {rang}")  # f-string
print(f"|{nom:<8}|{rang:>4}|")                             # alignements
text
Élève : Léa - moyenne : 15.756
Élève : Léa - rang : 2
Élève : Léa - moyenne : 15.76 - rang : 2
|Léa     |   2|

Dans une f-string (une chaîne précédée de f), chaque expression entre accolades est remplacée par sa valeur. Après le deux-points, un format : .2f pour deux décimales, <8 pour aligner à gauche sur 8 caractères, >4 à droite sur 4. Le programme n'exige pas cette mise en forme, mais elle rend tes affichages nets en quelques caractères.

Parcourir une chaîne

Comme une liste, une chaîne se parcourt caractère par caractère avec for. Compter les voyelles d'un mot :

text
Fonction NbVoyelles(mot : chaîne) : entier
    nb ← 0
    Pour chaque c dans mot Faire
        Si c est dans "aeiouy" Alors
            nb ← nb + 1
        FinSi
    FinPour
    Retourner nb
FinFonction
python
def nb_voyelles(mot):
    nb = 0
    for c in mot.lower():
        if c in "aeiouyàâéèêëîïôùûü":
            nb = nb + 1
    return nb


print(nb_voyelles("Algorithmique"), nb_voyelles("Réseaux"))
text
6 4

mot.lower() évite d'oublier les majuscules, et la chaîne de voyelles inclut les lettres accentuées : en français, sans elles, « Réseaux » n'aurait que 3 voyelles.

Construire une chaîne suit le motif de l'accumulateur, avec la chaîne vide comme point de départ. Inverser un mot sans [::-1] :

python
def inverser(mot):
    resultat = ""
    for c in mot:
        resultat = c + resultat   # chaque caractère passe devant
    return resultat


print(inverser("routeur"))
text
ruetuor

Codes des caractères

Chaque caractère a un code numérique : c'est ce nombre que l'ordinateur stocke. Python utilise Unicode, dont les 128 premiers codes reprennent l'ancienne table ASCII. ord(c) donne le code d'un caractère, chr(n) le caractère d'un code.

CaractèreABZaz0espaceé
Code656690971224832233

Les lettres majuscules ont des codes consécutifs, de 65 à 90 : la position d'une majuscule dans l'alphabet (A = 0, B = 1… Z = 25) est ord(c) - 65. C'est la base des chiffrements qui suivent. C'est aussi pourquoi "Z" < "a" : 90 est inférieur à 97.

Le code de César

Le code de César remplace chaque lettre par celle qui se trouve k rangs plus loin dans l'alphabet, en revenant au début après Z. La clé k est un entier de 1 à 25. Avec k = 3, A devient D, B devient E, et X devient A.

texte clair (position de 0 à 25)0AD1BE2CF3DG4EH5FI6GJ7HK8IL9JM10KN11LO12MP13NQ14OR15PS16QT17RU18SV19TW20UX21VY22WZ23XA24YB25ZCtexte chiffré avec la clé 3 : X, Y et Z reviennent au débutposition chiffrée = (position claire + 3) MOD 26
Le code de César avec la clé 3 : chaque lettre avance de 3 rangs, et le MOD 26 fait revenir au début de l'alphabet

Si p est la position d'une lettre (A = 0), sa position chiffrée est c = (p + k) MOD 26. Le MOD 26 réalise le retour au début : pour X (23) avec k = 3, (23 + 3) MOD 26 = 0, soit A. Pour déchiffrer, on recule : p = (c - k) MOD 26, ce qui revient à chiffrer avec la clé -k.

text
Fonction Cesar(texte : chaîne, k : entier) : chaîne
// Chiffre les lettres majuscules ; les autres caractères sont recopiés
    resultat ← ""
    Pour chaque c dans texte Faire
        Si c ≥ "A" ET c ≤ "Z" Alors
            p ← code(c) - 65
            resultat ← resultat + caractere((p + k) MOD 26 + 65)
        Sinon
            resultat ← resultat + c
        FinSi
    FinPour
    Retourner resultat
FinFonction
python
def cesar(texte, k):
    """Chiffre les majuscules de texte avec la clé k ; le reste est recopié."""
    resultat = ""
    for c in texte:
        if "A" <= c <= "Z":
            p = ord(c) - 65
            resultat = resultat + chr((p + k) % 26 + 65)
        else:
            resultat = resultat + c
    return resultat


chiffre = cesar("BONJOUR", 3)
print(chiffre)
print(cesar(chiffre, -3))
print(cesar("BTS SIO", 13), cesar(cesar("BTS SIO", 13), 13))
text
ERQMRXU
BONJOUR
OGF FVB BTS SIO

Avec k = 13 (le ROT13), chiffrer deux fois redonne le texte de départ, puisque 13 + 13 = 26. En Python, % renvoie toujours un reste positif quand le diviseur est positif : (1 - 3) % 26 vaut 24, et le déchiffrement avec une clé négative fonctionne sans précaution. En C# ou en JavaScript, il faudrait écrire ((p - k) % 26 + 26) % 26, car leur % peut être négatif (chapitre 2).

Casser un message chiffré par force brute, c'est une boucle sur les 25 clés :

python
def cesar(texte, k):
    resultat = ""
    for c in texte:
        if "A" <= c <= "Z":
            resultat = resultat + chr((ord(c) - 65 + k) % 26 + 65)
        else:
            resultat = resultat + c
    return resultat


message = "WKH SDVVZRUG LV VLR"
for k in range(1, 26):
    essai = cesar(message, -k)
    if "PASSWORD" in essai:
        print("Clé", k, ":", essai)
text
Clé 3 : THE PASSWORD IS SIO

Ici, on cherche un mot attendu dans le texte déchiffré. Sans indice, on afficherait les 25 essais et on lirait celui qui a un sens.

Le chiffrement affine

Le chiffrement affine généralise César : c = (a × p + b) MOD 26, avec deux clés a et b. César est le cas a = 1. Pour que deux lettres différentes ne donnent jamais la même lettre chiffrée, a doit être premier avec 26, c'est-à-dire PGCD(a, 26) = 1 : a est impair et n'est pas 13. Les valeurs possibles sont 1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23 et 25.

Avec a = 5 et b = 8, chiffrons BTS SIO :

Lettrep5p + 8MOD 26Lettre chiffrée
B11313N
T1910325Z
S189820U
I84822W
O14780A

BTS SIO devient NZU UWA.

Pour déchiffrer, il faut résoudre c ≡ a × p + b (mod 26), donc p ≡ a' × (c − b) (mod 26), où a' est l'inverse de a modulo 26 : l'entier tel que a × a' MOD 26 = 1. Pour a = 5, a' = 21, car 5 × 21 = 105 = 4 × 26 + 1. On le trouve en essayant les entiers de 1 à 25, ou avec pow(a, -1, 26) en Python. Le calcul de l'inverse modulaire est détaillé au cours d'arithmétique (inverse modulaire), avec pourquoi a doit être premier avec 26 (chiffrement affine).

python
def affine(texte, a, b):
    resultat = ""
    for c in texte:
        if "A" <= c <= "Z":
            p = ord(c) - 65
            resultat = resultat + chr((a * p + b) % 26 + 65)
        else:
            resultat = resultat + c
    return resultat


def inverse_mod26(a):
    """Renvoie l'inverse de a modulo 26, ou -1 s'il n'existe pas."""
    for x in range(1, 26):
        if a * x % 26 == 1:
            return x
    return -1


def affine_dechiffre(texte, a, b):
    a_inv = inverse_mod26(a)
    resultat = ""
    for c in texte:
        if "A" <= c <= "Z":
            p = (a_inv * (ord(c) - 65 - b)) % 26
            resultat = resultat + chr(p + 65)
        else:
            resultat = resultat + c
    return resultat


chiffre = affine("BTS SIO", 5, 8)
print(chiffre, inverse_mod26(5), affine_dechiffre(chiffre, 5, 8))
text
NZU UWA 21 BTS SIO

inverse_mod26 est une recherche séquentielle sur les 25 candidats. Si a n'est pas premier avec 26 (par exemple a = 4), aucun x ne convient et la fonction renvoie −1 : le message ne pourrait pas être déchiffré, car deux lettres auraient le même chiffré. Les sujets d'algorithmique et d'arithmétique du BTS utilisent volontiers ces chiffrements : le programme cite « codage, cryptage et décryptage » parmi les thèmes d'exercices.

Exercices

Exercice 1 sur 6 Extractions Réussi À revoir

Avec s = "ticket-2026-00421", écris l'expression Python qui donne : (1) "ticket" ; (2) l'année "2026" ; (3) le numéro "00421" puis l'entier 421 ; (4) la longueur ; (5) la chaîne en majuscules.

Voir la réponseMasquer la réponse de l'exercice 1
  1. s[:6], ou s.split("-")[0].
  2. s[7:11], ou s.split("-")[1].
  3. s[-5:] donne "00421", et int(s[-5:]) donne 421 : la conversion supprime les zéros de tête.
  4. len(s) vaut 17.
  5. s.upper() donne "TICKET-2026-00421".

La version avec split est plus robuste : elle reste juste si le numéro passe à six chiffres.

Tu avais trouvé ?

Exercice 2 sur 6 Palindrome Réussi À revoir

Un palindrome se lit de la même façon dans les deux sens : « kayak », « radar ». Écris une fonction est_palindrome(mot) sans utiliser [::-1], en comparant le premier et le dernier caractère, puis le deuxième et l'avant-dernier, etc. Puis adapte-la pour ignorer les espaces et les majuscules : « Engage le jeu que je le gagne » doit être reconnu.

Voir la réponseMasquer la réponse de l'exercice 2
python
def est_palindrome(mot):
    n = len(mot)
    for i in range(n // 2):
        if mot[i] != mot[n - 1 - i]:
            return False
    return True


def est_palindrome_phrase(phrase):
    lettres = phrase.lower().replace(" ", "")
    return est_palindrome(lettres)


print(est_palindrome("kayak"), est_palindrome("réseau"))
print(est_palindrome_phrase("Engage le jeu que je le gagne"))

Résultat : True False puis True. On s'arrête au milieu (n // 2 comparaisons), comme pour l'inversion d'un tableau. La deuxième fonction réutilise la première après avoir normalisé la phrase.

Tu avais trouvé ?

Exercice 3 sur 6 Valider une adresse IPv4 Réussi À revoir

Écris une fonction ip_valide(ip) qui renvoie True si la chaîne a la forme d'une adresse IPv4 : quatre nombres séparés par des points, chacun composé uniquement de chiffres et compris entre 0 et 255. Teste avec "192.168.1.10", "10.0.0", "256.1.1.1", "192.168.a.1" et "172.16.0.1".

Voir la réponseMasquer la réponse de l'exercice 3
python
def ip_valide(ip):
    octets = ip.split(".")
    if len(octets) != 4:
        return False
    for o in octets:
        if not o.isdigit():
            return False
        if int(o) > 255:
            return False
    return True


for essai in ["192.168.1.10", "10.0.0", "256.1.1.1", "192.168.a.1", "172.16.0.1"]:
    print(essai, ip_valide(essai))
text
192.168.1.10 True
10.0.0 False
256.1.1.1 False
192.168.a.1 False
172.16.0.1 True

Le test isdigit() vient avant int(o) : sinon int("a") lèverait une ValueError. Une chaîne vide (cas "192..1.1") n'est pas composée de chiffres : "".isdigit() vaut False, elle est donc rejetée. isdigit() refuse aussi le signe moins, donc les négatifs.

Tu avais trouvé ?

Exercice 4 sur 6 Générer un identifiant Réussi À revoir

Le service informatique crée les identifiants ainsi : la première lettre du prénom, suivie du nom, le tout en minuscules, sans espace ni tiret, limité à 8 caractères. Écris la fonction identifiant(prenom, nom). Que donnent « Jean », « Dupont » et « Marie », « Le Goff-Martin » ?

Voir la réponseMasquer la réponse de l'exercice 4
python
def identifiant(prenom, nom):
    brut = prenom[0] + nom
    brut = brut.lower().replace(" ", "").replace("-", "")
    return brut[:8]


print(identifiant("Jean", "Dupont"))
print(identifiant("Marie", "Le Goff-Martin"))

Résultats : jdupont et mlegoffm. Le découpage [:8] fonctionne même si la chaîne est plus courte. Une vraie fonction devrait aussi retirer les accents et gérer les homonymes (en ajoutant un chiffre), deux améliorations classiques à proposer à l'oral.

Tu avais trouvé ?

Exercice 5 sur 6 Déchiffrer un message de César Réussi À revoir

Le message "VDXYHJDUGH-QRFWXUQH RN D PLQXLW" a été chiffré avec la clé 3. Déchiffre-le à la main pour les premières lettres, puis avec la fonction cesar. Explique pourquoi le tiret et les espaces sont conservés.

Voir la réponseMasquer la réponse de l'exercice 5

V (21) − 3 = 18 : S ; D (3) − 3 = 0 : A ; X (23) − 3 = 20 : U. Avec cesar("VDXYHJDUGH-QRFWXUQH RN D PLQXLW", -3), on obtient SAUVEGARDE-NOCTURNE OK A MINUIT. Le tiret et les espaces ne sont pas des majuscules : la branche Sinon les recopie tels quels. Inconvénient : les espaces révèlent la longueur des mots et facilitent l'attaque.

Tu avais trouvé ?

Exercice 6 sur 6 Compter les mots d'une phrase Réussi À revoir

Écris une fonction qui renvoie le nombre de mots d'une phrase, sans utiliser split, en comptant les passages d'une espace à une lettre. Teste avec " Le BTS SIO ", qui contient 3 mots.

Voir la réponseMasquer la réponse de l'exercice 6

Un mot commence quand on lit un caractère qui n'est pas une espace, alors que le précédent en était une (ou qu'on est au début).

python
def nb_mots(phrase):
    nb = 0
    dans_un_mot = False
    for c in phrase:
        if c != " " and not dans_un_mot:
            nb = nb + 1
            dans_un_mot = True
        elif c == " ":
            dans_un_mot = False
    return nb


print(nb_mots("  Le BTS   SIO  "), len("  Le BTS   SIO  ".split()))

Résultat : 3 3. La variable booléenne dans_un_mot mémorise l'état du caractère précédent : c'est une technique courante pour détecter un changement en parcourant une séquence. Compter les espaces ne marcherait pas : il y en a 8 ici.

Tu avais trouvé ?

Chapitre terminé !

Fais les 6 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Recherche dichotomique et algorithmes de triRevoir le sommaire du cours