Aller au contenu

Accueil › BCPST › Informatique

BCPST1 · Informatique · semaine 3

Tableaux unidimensionnels et fichiers texte

Recherche d'un élément, du maximum et du second maximum, indexation et extraction dans une liste. Lecture et écriture dans un fichier texte.

Sommaire

Le cours

Résumé en cinq points

  1. Un tableau unidimensionnel est une liste, indicée de 00 à n−1n - 1 avec nn = len(t). Une tranche t[i:j] est une copie des éléments d'indices ii à j−1j - 1.
  2. L'affectation u = t crée un alias, pas une copie.
  3. La recherche d'un élément et la recherche du maximum font un parcours, leur coût est linéaire. Le maximum s'initialise avec le premier élément.
  4. Pour la convention 1 (doublons comptés), le second maximum se calcule en un parcours avec deux variables, en faisant descendre l'ancien maximum. La convention est à préciser, la convention 2 demande deux parcours.
  5. Un fichier texte se lit avec open, readline, readlines et split, s'écrit avec write en mode "w" et se ferme avec close. Les valeurs lues sont des chaînes à convertir avec float ou int.
Pourquoi ce chapitre

En biologie, en géologie comme en chimie, les données arrivent en séries :

  • les masses de vingt souris d'un élevage ;
  • les températures relevées chaque heure dans une mare ;
  • les longueurs de feuilles mesurées sur un échantillon.

En Python, une telle série se range dans un tableau à une dimension, c'est-à-dire une liste. Retrouver une valeur, repérer la plus grande, extraire une partie : ces gestes reviennent dans tous les travaux pratiques et dans toutes les épreuves.

Ces séries ne sont presque jamais tapées à la main. Elles sont stockées dans des fichiers texte, produits par un appareil de mesure, un tableur ou une base de données publique. Ce chapitre apprend donc à lire et écrire ces fichiers, puis à parcourir les tableaux obtenus avec des boucles simples, en écrivant des fonctions propres et testées.

1Le tableau unidimensionnel en Python

1.1Listes et indices

Définition 1.1Tableau unidimensionnel

En Python, un tableau unidimensionnel se représente par une liste. Ses éléments sont repérés par des indices entiers qui commencent à 0.

Pour une liste t de longueur nn, obtenue par len(t), les indices valides vont de 00 à n−1n - 1. L'annexe ne rend exigible que l'accès par indice positif valide, sans indice négatif.

Attention

Lire t[n] provoque une erreur d'indice. C'est l'erreur la plus fréquente du chapitre.

masses = [23.1, 25.4, 22.8, 26.0, 24.3]   # masses en grammes
len(masses)      # 5
masses[0]        # 23.1, premier élément
masses[4]        # 24.3, dernier élément, indice len(masses) - 1
masses[2] = 22.9 # modification d'un élément

1.2Créer une liste

Trois façons, toutes dans l'annexe :

  • par append successifs, à partir de la liste vide : t = [] puis t.append(x) ajoute x à la fin ;
  • par compréhension, [e for x in s] : par exemple [x * 1000 for x in masses] convertit les masses de grammes en milligrammes ;
  • par répétition, [0] * n donne une liste de nn zéros, utile pour préparer des compteurs.

1.3Parcourir une liste

Deux types de boucles, à choisir selon le besoin.

for x in t:                 # parcours des valeurs
    ...
for i in range(len(t)):     # parcours des indices, t[i] est la valeur
    ...

Le parcours par valeur est plus lisible quand seule la valeur compte. Le parcours par indice est nécessaire quand on veut la position, ou comparer un élément à son voisin t[i + 1]. L'indice doit alors s'arrêter à n−2n - 2, avec range(len(t) - 1).

2Manipulations élémentaires, indexation et extraction

2.1Tranches

Définition 2.1Tranche

L'expression t[i:j] renvoie une nouvelle liste formée des éléments d'indices ii inclus à jj exclu. Elle contient j−ij - i éléments quand 0⩽i⩽j⩽n0 \leqslant i \leqslant j \leqslant n.

On peut omettre une borne : t[:k] donne les kk premiers éléments, t[k:] tous les éléments à partir de l'indice kk. La même syntaxe s'applique aux chaînes de caractères.

releves = [12, 14, 15, 17, 16, 13, 11]
releves[2:5]     # [15, 17, 16]
releves[:3]      # [12, 14, 15]
releves[3:]      # [17, 16, 13, 11]

Une tranche est une copie. Modifier releves[2:5] une fois extraite ne modifie pas releves. Son coût est proportionnel à sa longueur.

2.2Concaténation, répétition, retrait en fin

  • t + u renvoie une nouvelle liste formée des éléments de t suivis de ceux de u ;
  • t * 3 répète t trois fois ;
  • t.pop() retire et renvoie le dernier élément.

Ces opérations permettent par exemple de fusionner les relevés de deux journées ou de retirer une dernière mesure aberrante.

2.3Copie et alias

L'affectation u = t ne crée pas de nouvelle liste. u et t désignent la même liste, et modifier u[0] modifie aussi t[0]. Pour obtenir une liste indépendante, on écrit u = t.copy() ou u = t[:].

Attention

Une fonction qui modifie une liste reçue en argument modifie la liste de l'appelant. La distinction entre copie et alias est approfondie au second semestre.

2.4Extraire selon une condition

Pour extraire les valeurs qui vérifient une condition, on parcourt la liste et l'on ajoute les bonnes valeurs à une nouvelle liste.

def superieures(t, seuil):
    # t : liste de nombres. Renvoie la liste des éléments
    # de t strictement supérieurs à seuil, dans l'ordre.
    res = []
    for x in t:
        if x > seuil:
            res.append(x)
    return res

La compréhension avec condition, [x for x in t if x > seuil], fait la même chose en une ligne. Elle est très utilisée, mais elle n'est pas listée dans l'annexe et devrait être accompagnée d'une explication dans un sujet.

2.5Construire une liste à partir d'une autre par les indices

Beaucoup de manipulations élémentaires consistent à calculer une nouvelle liste case par case à partir des indices de l'ancienne. Deux exemples.

def renverse(t):
    # Nouvelle liste : éléments de t dans l'ordre inverse.
    n = len(t)
    return [t[n - 1 - i] for i in range(n)]

def differences(t):
    # Écarts entre éléments consécutifs, longueur len(t) - 1.
    return [t[i + 1] - t[i] for i in range(len(t) - 1)]

La seconde fonction sert par exemple à calculer la croissance d'une population d'une année sur l'autre à partir des effectifs annuels.

Attention

Le point délicat est toujours la borne.

  • Pour renverse, l'indice n−1−in - 1 - i parcourt bien les valeurs de n−1n - 1 à 00.
  • Pour differences, ii s'arrête à n−2n - 2, faute de quoi t[i + 1] sortirait de la liste.

3Recherche d'un élément

3.1Présence

On veut savoir si une valeur x apparaît dans une liste t. On parcourt la liste et l'on s'arrête dès qu'on la trouve.

def est_present(t, x):
    # Renvoie True si x est un élément de t, False sinon.
    for y in t:
        if y == x:
            return True
    return False

Attention

Le return False est placé après la boucle. Placé dans la boucle, dans un else, il ferait répondre False dès le premier élément différent de x. Cette erreur est très fréquente.

3.2Position

Variante : renvoyer l'indice de la première occurrence, ou −1-1 si la valeur est absente. Cette convention doit toujours être précisée.

def premier_indice(t, x):
    for i in range(len(t)):
        if t[i] == x:
            return i
    return -1
  • Pour la dernière occurrence, on parcourt sans s'arrêter et l'on garde le dernier indice trouvé.
  • Pour le nombre d'occurrences, on incrémente un compteur.

3.3Coût

Dans le pire des cas, valeur absente ou en dernière position, on examine les nn éléments. Le nombre d'opérations est proportionnel à nn : on dit que le coût est linéaire. Doubler la taille du tableau double le temps de recherche.

On verra plus tard qu'une recherche dichotomique, possible sur un tableau trié, est beaucoup plus rapide.

4Recherche du maximum

4.1Valeur du maximum

On garde dans une variable la plus grande valeur rencontrée jusqu'ici, initialisée avec le premier élément, ce qui suppose la liste non vide.

def maximum(t):
    # t : liste non vide de nombres. Plus grande valeur.
    m = t[0]
    for x in t:
        if x > m:
            m = x
    return m

Attention

Initialiser m à 0 est une erreur classique. Pour une liste de températures toutes négatives, la fonction renverrait 0, qui n'est pas dans la liste.

Propriété 4.1Invariant de boucle

Après avoir examiné les kk premiers éléments, m est le maximum de ces kk éléments.

C'est vrai au départ, et chaque tour de boucle le conserve. À la fin, m est le maximum de toute la liste. Une telle propriété s'appelle un invariant de boucle.

4.2Position du maximum

Souvent, l'information utile n'est pas la valeur mais la position, par exemple l'heure du pic de température. On garde alors l'indice.

def indice_max(t):
    im = 0
    for i in range(1, len(t)):
        if t[i] > t[im]:
            im = i
    return im

Avec la comparaison stricte, en cas d'égalité, on obtient la première position du maximum. Avec >=, on obtiendrait la dernière. Le minimum s'obtient en inversant la comparaison.

4.3Second maximum

Le second maximum demande de fixer une convention.

  • Convention 1 : la deuxième valeur de la liste rangée par ordre décroissant, doublons compris. Le second maximum de [7, 9, 9, 4] est 9.
  • Convention 2 : la plus grande valeur strictement inférieure au maximum. Elle vaut ici 7.

L'énoncé doit préciser la convention.

Pour la convention 1, on garde deux variables, m1 la plus grande valeur vue et m2 la deuxième. On suppose au moins deux éléments.

def second_maximum(t):
    # t : liste d'au moins deux nombres. Doublons comptés.
    if t[0] >= t[1]:
        m1 = t[0]
        m2 = t[1]
    else:
        m1 = t[1]
        m2 = t[0]
    for i in range(2, len(t)):
        x = t[i]
        if x > m1:
            m2 = m1
            m1 = x
        elif x > m2:
            m2 = x
    return m2

Attention

L'erreur fréquente est d'oublier m2 = m1 quand un nouveau maximum apparaît. L'ancien maximum devient alors le second, il ne doit pas être perdu.

Un seul parcours suffit, le coût est linéaire.

Pour la convention 2, on peut calculer le maximum, puis chercher le maximum des valeurs qui lui sont strictement inférieures, en deux parcours. Il faut alors prévoir le cas où toutes les valeurs sont égales.

5Lecture et écriture dans un fichier texte

5.1Fichier texte et fichier de données

Définition 5.1Fichier texte

Un fichier texte est une suite de caractères organisée en lignes, chaque ligne se terminant par un caractère de fin de ligne, noté "\n" en Python.

Un fichier de données comporte souvent une ligne d'en-tête décrivant les colonnes, puis une ligne par individu ou par mesure. Les valeurs sont séparées par un caractère fixé : point-virgule, virgule ou tabulation.

individu;masse_g;longueur_mm
1;23.1;87
2;25.4;91
3;22.8;85

5.2Les fonctions utiles

L'annexe du programme de BCPST ne liste pas ces fonctions. Un sujet qui les utilise en donne la documentation. Il faut néanmoins savoir les employer couramment en travaux pratiques.

  • f = open(nom, "r") ouvre le fichier nom en lecture, "w" l'ouvre en écriture en effaçant son contenu, "a" en ajout à la fin.
  • f.read() renvoie tout le contenu dans une seule chaîne.
  • f.readline() renvoie la ligne suivante, fin de ligne comprise, ou une chaîne vide à la fin du fichier.
  • f.readlines() renvoie la liste des lignes restantes.
  • f.write(s) écrit la chaîne s, sans ajouter de fin de ligne.
  • f.close() ferme le fichier, indispensable en écriture pour que les données soient réellement enregistrées.
  • s.split(sep) découpe la chaîne s selon le séparateur sep et renvoie la liste des morceaux.

La lecture avance dans le fichier. Après un readline qui a lu l'en-tête, readlines ne renvoie que les lignes de données.

5.3Des chaînes aux nombres

Tout ce qu'on lit dans un fichier est du texte. La chaîne "23.1" doit être convertie avec float, et "87" avec int, avant tout calcul. Sinon, "23.1" + "25.4" donne la chaîne "23.125.4".

Attention

Deux autres pièges.

  • Avec split(";"), le dernier morceau garde le "\n" final. float et int le tolèrent, mais une comparaison de chaînes échoue à cause du "\n".
  • Un fichier issu d'un tableur réglé en français peut contenir des virgules décimales, "23,1", que float refuse. Il faut les remplacer par des points avant conversion, avec une fonction documentée.

5.4Exemple de lecture

def lire_masses(nom):
    # nom : fichier individu;masse_g;longueur_mm avec en-tête.
    # Renvoie la liste des masses, en flottants.
    f = open(nom, "r")
    f.readline()                    # on saute l'en-tête
    masses = []
    for ligne in f.readlines():
        morceaux = ligne.split(";")
        if len(morceaux) == 3:      # on ignore une ligne vide
            masses.append(float(morceaux[1]))
    f.close()
    return masses

On combine ensuite lecture et recherche : indice_max(lire_masses("souris.csv")) donne la position de l'individu le plus lourd.

5.5Écriture

Pour enregistrer des résultats, on ouvre en mode "w", on écrit ligne par ligne en convertissant les nombres avec str et en ajoutant "\n", puis on ferme.

def ecrire_selection(nom, valeurs):
    f = open(nom, "w")
    f.write("masse_g\n")
    for v in valeurs:
        f.write(str(v) + "\n")
    f.close()

Attention

Ouvrir en "w" un fichier existant l'efface immédiatement. Ouvrir par erreur en écriture le fichier de mesures qu'on voulait lire fait perdre les données.

Méthodes

MéthodeRéflexes du chapitre

  1. Avant d'écrire une boucle, décider si l'on parcourt les valeurs ou les indices, et vérifier les bornes sur une liste de deux éléments.
  2. Pour une recherche, placer le return du cas « non trouvé » après la boucle.
  3. Pour un maximum, initialiser avec le premier élément, jamais avec 0, et énoncer l'invariant.
  4. Pour le second maximum, préciser la convention et gérer la descente de l'ancien maximum.
  5. Ouvrir le fichier de données dans un éditeur avant de programmer : repérer l'en-tête, le séparateur, le séparateur décimal.
  6. Lire, découper, convertir, dans cet ordre, puis fermer le fichier.
  7. Tester chaque fonction sur un petit exemple dont on connaît le résultat, y compris un cas limite.

Ce qu'il faut savoir refaire en khôlle

  1. Donner les indices valides d'une liste, expliquer une tranche t[i:j] et le nombre de ses éléments.
  2. Expliquer la différence entre u = t et u = t.copy().
  3. Écrire la recherche d'un élément, en version booléenne et en version indice, et donner son coût.
  4. Écrire la recherche du maximum et de sa position, et justifier l'algorithme par un invariant.
  5. Écrire le calcul du second maximum dans une convention donnée, et discuter l'autre convention.
  6. Extraire d'une liste les éléments qui vérifient une condition.
  7. Lire un fichier de données avec en-tête et en extraire une colonne sous forme de liste de flottants.
  8. Écrire une liste de résultats dans un fichier texte.

Méthodes et exercices

Les résultats à connaître

En informatique, les « résultats » sont trois choses : le comportement exact du langage, la correction des algorithmes et leur nombre d'opérations dans le pire cas.

  • Une liste t de longueur n a pour indices valides les entiers de 0 à n − 1. Lire ou écrire t[n] provoque une erreur : exigible.
  • Pour 0 ≤ i ≤ j ≤ n, la tranche t[i:j] est une nouvelle liste de j − i éléments. Son coût est proportionnel à j − i : exigible.
  • Une tranche dont les bornes dépassent la longueur ne provoque pas d'erreur. Elle est simplement tronquée : à savoir reconnaître.
  • t.append(x) et t.pop() agissent en fin de liste avec un coût considéré comme constant. t + u crée une nouvelle liste, pour un coût proportionnel à len(t) + len(u) : exigible.
  • u = t crée un alias. t.copy() et t[:] créent une copie indépendante : exigible.
  • Une fonction qui modifie une liste reçue en argument modifie la liste de l'appelant : exigible.
  • Recherche séquentielle : elle est correcte et fait au plus n comparaisons. Le coût est linéaire dans le pire cas : exigible avec justification par invariant.
  • Maximum, minimum et leur position : un parcours, n − 1 comparaisons, sur une liste non vide : exigible avec invariant.
  • Second maximum, doublons comptés : un parcours, au plus 2n comparaisons, sur une liste d'au moins deux éléments : exigible avec invariant.
  • Extraction selon une condition : un parcours, et l'ordre des éléments est conservé : exigible.
  • Fichier texte : la lecture est séquentielle, tout ce qu'on lit est une chaîne, et le mode "w" efface le fichier dès l'ouverture. Il faut savoir employer ces fonctions, mais un sujet les fournit avec leur documentation.
  • Hors programme signalé : indices négatifs, fonctions max, min, sum et sorted, méthodes index, count et sort, construction with open, compréhension avec condition. Il est prudent de ne pas compter sur le test x in t quand l'énoncé demande d'écrire la recherche.

Clés de démonstration

Recherche d'un élément. Invariant : au début du tour d'indice k, x n'est égal à aucun des éléments t[0] à t[k − 1]. Si la boucle se termine sans return, x n'est donc égal à aucun élément. L'astuce est de voir que le return True est sûr dès qu'il se déclenche, et que seul le cas « non trouvé » demande l'invariant.

Maximum et position du maximum. Invariant : après examen des k premiers éléments, t[im] est le maximum de t[0:k] et im la première position où il est atteint. L'initialisation im = 0 le rend vrai pour k = 1. L'astuce est la comparaison stricte, qui préserve la première position en cas d'égalité.

Second maximum, doublons comptés. Invariant : après examen des k premiers éléments, m1 et m2 sont les deux premières valeurs de t[0:k] rangée par ordre décroissant. Quand x dépasse m1, l'ancien m1 devient le deuxième. Quand x est entre m2 et m1, seul m2 change. L'astuce est de traiter les deux premiers éléments à part, pour que l'invariant soit vrai dès k = 2.

Extraction. Invariant : après examen de t[0:k], res est la liste des éléments de t[0:k] qui vérifient la condition, dans leur ordre d'apparition. L'astuce est append, qui ajoute en fin et conserve donc l'ordre.

Coût. On compte les tours de boucle et le travail fait à chaque tour. Un parcours complet avec un travail constant par tour donne un coût proportionnel à n. Attention au travail caché : une tranche ou une concaténation dans une boucle coûte autant que sa longueur.

Terminaison d'un while à deux indices. La quantité entière j − i décroît strictement à chaque tour. Elle finit donc par devenir négative ou nulle, et la boucle s'arrête. L'astuce est de choisir la condition i < j, qui couvre les longueurs paires et impaires.

Exemples et contre-exemples à connaître

  1. Avec m initialisé à 0, le maximum de [−3, −1, −7] vaut 0, valeur absente de la liste. C'est le cas d'école de la mauvaise initialisation.
  2. Si le return False est placé dans un else à l'intérieur de la boucle, est_present([4, 7], 7) renvoie False, car la fonction s'arrête dès 4.
  3. Après u = t puis u.append(5), t contient aussi 5. Les deux noms désignent la même liste.
  4. Pour t de longueur 3, t[3] provoque une erreur, mais t[2:5] renvoie une liste d'un seul élément. Une tranche ne signale pas un dépassement.
  5. Pour [7, 9, 9, 4], le second maximum vaut 9 si l'on compte les doublons, et 7 sinon. Sans convention, la question n'a pas de sens.
  6. La ligne "1;23.1;87\n" découpée selon ";" donne ['1', '23.1', '87\n']. Le test '87\n' == '87' vaut False, alors que int('87\n') vaut bien 87.
  7. "23.1" + "25.4" vaut "23.125.4". On a oublié de convertir en nombres.
  8. float("23,1") provoque une erreur. La virgule décimale d'un tableur français doit être remplacée par un point.
  9. Deux appels f.write("12.5") puis f.write("13.1") écrivent 12.513.1 sur une seule ligne. write n'ajoute pas de fin de ligne.
  10. Après un readlines, un readline renvoie la chaîne vide. La lecture a déjà atteint la fin du fichier.

Techniques du chapitre

Technique 1, tester les bornes sur de toutes petites listes. Mini-exercice : combien de tours fait for i in range(len(t) - 1) pour t = [5] et pour t = [] ? Réponse : zéro dans les deux cas, car range d'un entier négatif ou nul est vide. Une comparaison de voisins ne plante donc pas sur ces listes.

Technique 2, garder un indice plutôt qu'une valeur. Mini-exercice : trouver l'heure du minimum de températures relevées de 0 h à 23 h. On adapte indice_max en remplaçant > par <. L'indice renvoyé est l'heure, et la valeur s'obtient ensuite par t[i].

Technique 3, sortir dès que la réponse est connue. Mini-exercice : tester que toutes les mesures sont positives. On parcourt et l'on renvoie False au premier x < 0. On place return True après la boucle. Une liste vide donne True, ce qui est la convention logique.

Technique 4, un compteur qu'on remet à zéro. Mini-exercice : dans une série de mesures, compter les montées, c'est-à-dire les i tels que t[i + 1] > t[i]. On parcourt range(len(t) - 1) et on incrémente c à chaque montée. Pour [1, 3, 2, 5], on obtient 2.

Technique 5, un tableau de compteurs indexé par les valeurs. Mini-exercice : compter les notes entières de 0 à 20. On crée c = [0] * 21, puis pour chaque note x on fait c[x] = c[x] + 1. La longueur 21 vient des 21 valeurs possibles.

Technique 6, lire, découper, nettoyer, convertir. Mini-exercice : extraire la masse de la ligne "3;22,8;85\n". On découpe selon le point-virgule et l'on prend le morceau d'indice 1, qui vaut "22,8". On remplace la virgule par un point, puis float donne 22.8.

Technique 7, écrire la spécification avant le code. Mini-exercice : spécifier une fonction position(t, x). On écrit en commentaire la nature de t, le résultat, et la convention −1 si x est absent. Le cas limite se déduit de la spécification : pour une liste vide, on renvoie −1.

Exercices classiques corrigés

Choisis ton niveau. Difficulté indicative ; les exercices qui se suivent restent dans l'ordre.

Exercice 1

Classiques de khôlle et concours · Khôlle

Écrire une fonction qui renvoie l'indice de la dernière occurrence de x dans t, ou −1 si x est absent. En déduire le nombre d'occurrences en modifiant deux lignes.

Technique parcours complet et mémorisation du dernier indice.

Voir le corrigé
  1. Idée : on ne peut pas s'arrêter à la première occurrence. On parcourt toute la liste et l'on écrase res à chaque occurrence.
  2. Code :
def derniere_occurrence(t, x):
    # Indice de la dernière occurrence de x dans t, ou -1.
    res = -1
    for i in range(len(t)):
        if t[i] == x:
            res = i
    return res
  1. Invariant : après le tour i, res est le dernier indice au plus i où x apparaît, ou −1 s'il n'y en a pas.
  2. Variante : on remplace res = -1 par res = 0, et res = i par res = res + 1. Le return res est inchangé et renvoie le nombre d'occurrences.
  3. Test : pour [2, 5, 2, 7] et x = 2, on obtient 2. Le coût est de n comparaisons.

piège : Écrire return i dans la boucle, ce qui renvoie la première occurrence.

Exercice 2

Classiques de khôlle et concours · Khôlle

Calculer en un seul parcours l'amplitude d'une série non vide de températures, c'est-à-dire le maximum moins le minimum.

Technique deux variables extrêmes mises à jour ensemble.

Voir le corrigé
  1. Idée : on initialise mini et maxi avec t[0], puis on met à jour l'une ou l'autre.
  2. Code :
def amplitude(t):
    # t : liste non vide de nombres.
    mini = t[0]
    maxi = t[0]
    for x in t:
        if x > maxi:
            maxi = x
        elif x < mini:
            mini = x
    return maxi - mini
  1. Justification du elif : on a toujours mini ≤ maxi. Un x qui dépasse maxi dépasse donc aussi mini et ne peut pas être un nouveau minimum.
  2. Test : [3, −2, 5] donne 5 − (−2) = 7. Une liste d'un élément donne 0.

piège : Initialiser mini à 0 et maxi à 0. Pour [3, 5], on obtient 5 au lieu de 2.

Exercice 3. Tester si une liste est triée par ordre croissant au sens large

Classiques de khôlle et concours · Khôlle

Technique comparaison de voisins et sortie anticipée.

Voir le corrigé
  1. Idée : la liste est croissante si et seulement si t[i] ≤ t[i + 1] pour tout i de 0 à n − 2.
  2. Code :
def est_croissante(t):
    for i in range(len(t) - 1):
        if t[i] > t[i + 1]:
            return False
    return True
  1. Cas limites : pour une liste vide ou réduite à un élément, la boucle ne tourne pas et l'on renvoie True.
  2. Coût : au plus n − 1 comparaisons.

piège : Boucler sur range(len(t)), ce qui lit t[n] au dernier tour et provoque une erreur d'indice.

Exercice 4

Classiques de khôlle et concours · Khôlle

Des températures maximales journalières sont rangées dans une liste. Renvoyer la longueur de la plus longue suite de jours consécutifs où la température dépasse strictement un seuil.

Technique compteur courant remis à zéro et record.

Voir le corrigé
  1. Idée : courante compte la série en cours et meilleure garde le record. On met le record à jour dès que la série s'allonge.
  2. Code :
def plus_longue_serie(t, seuil):
    meilleure = 0
    courante = 0
    for x in t:
        if x > seuil:
            courante = courante + 1
            if courante > meilleure:
                meilleure = courante
        else:
            courante = 0
    return meilleure
  1. Test : pour [30, 36, 37, 29, 36, 38, 39, 31] et un seuil de 35, les séries ont pour longueurs 2 puis 3. La fonction renvoie 3.

piège : Ne mettre le record à jour que dans le else. Une série qui termine la liste est alors oubliée, et [36, 37] renvoie 0.

Exercice 5

Classiques de khôlle et concours · Type écrit G2E

Écrire en un seul parcours le second maximum au sens strict, c'est-à-dire la plus grande valeur strictement inférieure au maximum. La fonction renvoie un couple (existe, valeur), avec existe égal à False si toutes les valeurs sont égales.

Technique invariant avec un indicateur booléen.

Voir le corrigé
  1. Invariant : m1 est le maximum de la partie examinée. Existe dit si cette partie contient une valeur strictement inférieure à m1, et dans ce cas m2 est la plus grande de ces valeurs.
  2. Code :
def second_strict(t):
    # t non vide. Renvoie (True, s) ou (False, 0).
    m1 = t[0]
    existe = False
    m2 = 0
    for x in t:
        if x > m1:
            m2 = m1
            existe = True
            m1 = x
        elif x < m1 and (not existe or x > m2):
            m2 = x
            existe = True
    return (existe, m2)
  1. Justification : quand x dépasse m1, toutes les valeurs vues sont au plus égales à l'ancien m1, qui devient donc le second. Une valeur égale à m1 ne change rien.
  2. Tests : [7, 9, 9, 4] donne (True, 7). [9, 3, 7] donne (True, 7). [5, 5] donne (False, 0).

piège : Initialiser m2 à 0 sans indicateur. Pour des valeurs toutes négatives, la valeur 0 n'est jamais battue.

Exercice 6

Classiques de khôlle et concours · Type écrit Agro-Véto

Effectifs. Une liste contient les nombres de graines comptés dans des gousses, entiers entre 0 et vmax. Renvoyer la liste c telle que c[v] soit le nombre de gousses à v graines, puis en déduire la valeur la plus fréquente.

Technique tableau de compteurs.

Voir le corrigé
  1. Idée : la valeur observée sert directement d'indice dans c. Un seul parcours suffit.
  2. Code :
def effectifs(t, vmax):
    # t : entiers entre 0 et vmax.
    c = [0] * (vmax + 1)
    for x in t:
        c[x] = c[x] + 1
    return c
  1. La valeur la plus fréquente est indice_max(effectifs(t, vmax)), avec la fonction de la fiche. En cas d'égalité, on obtient la plus petite valeur.
  2. Test : effectifs([2, 3, 2, 0], 3) vaut [1, 0, 2, 1] et la valeur la plus fréquente est 2. Le coût est proportionnel à n + vmax.

piège : Créer [0] * vmax. Une gousse à vmax graines provoque alors une erreur d'indice.

Exercice 7

Classiques de khôlle et concours · Type écrit Agro-Véto

Un fichier contient une ligne d'en-tête jour;station;temperature, puis des lignes comme j1;Lyon;12,5, avec des virgules décimales. On fournit s.replace(a, b), qui renvoie une copie de la chaîne s où chaque occurrence de a est remplacée par b. Renvoyer la liste des températures en flottants.

Technique lire, découper, nettoyer, convertir.

Voir le corrigé
  1. Idée : on saute l'en-tête, on découpe chaque ligne selon le point-virgule, on nettoie le troisième morceau, puis on convertit.
  2. Code :
def lire_temperatures(nom):
    f = open(nom, "r")
    f.readline()
    temp = []
    for ligne in f.readlines():
        morceaux = ligne.split(";")
        if len(morceaux) == 3:
            texte = morceaux[2].replace(",", ".")
            temp.append(float(texte))
    f.close()
    return temp
  1. Le dernier morceau garde la fin de ligne, que float tolère. Le test sur la longueur écarte une ligne vide en fin de fichier.
  2. Test : un fichier de deux lignes de données, 12,5 puis 14,0, donne [12.5, 14.0].

piège : Oublier de sauter l'en-tête. float("temperature") provoque alors une erreur à la première ligne.

Exercice 8

Classiques de khôlle et concours · Type écrit Agro-Véto

Écrire dans un fichier les valeurs d'une liste qui dépassent un seuil, avec leur indice. Le fichier commence par l'en-tête indice;valeur.

Technique écriture ligne par ligne avec str et fin de ligne.

Voir le corrigé
  1. Idée : on ouvre en mode "w", on écrit l'en-tête, puis une ligne par valeur retenue, et l'on ferme.
  2. Code :
def ecrire_anomalies(nom, t, seuil):
    f = open(nom, "w")
    f.write("indice;valeur\n")
    for i in range(len(t)):
        if t[i] > seuil:
            f.write(str(i) + ";" + str(t[i]) + "\n")
    f.close()
  1. Test : pour [12.5, 18.0, 11.2] et un seuil de 15, le fichier contient l'en-tête puis la ligne 1;18.0.
  2. Pour compléter un fichier existant sans l'effacer, on ouvrirait en mode "a" et l'on n'écrirait pas l'en-tête.

piège : Écrire str(i) + ";" + t[i]. Concaténer une chaîne et un flottant provoque une erreur.

Exercice 9

Approfondissement · Approfondissement, type oral

Moyenne glissante. Pour lisser une série, renvoyer la liste des moyennes des tranches t[i:i + k], pour 1 ≤ k ≤ len(t), avec un coût linéaire.

Technique fenêtre glissante, on corrige une somme au lieu de la recalculer.

Voir le corrigé
  1. Idée : il y a n − k + 1 fenêtres. Passer d'une fenêtre à la suivante ajoute t[i] et retire t[i − k].
  2. Code :
def moyenne_glissante(t, k):
    res = []
    s = 0
    for i in range(k):
        s = s + t[i]
    res.append(s / k)
    for i in range(k, len(t)):
        s = s + t[i] - t[i - k]
        res.append(s / k)
    return res
  1. Invariant : au tour i, après mise à jour, s est la somme de t[i − k + 1] à t[i].
  2. Test : [2, 4, 6, 8] avec k = 2 donne [3.0, 5.0, 7.0], soit trois moyennes.
  3. Coût : environ 2n opérations, contre environ n × k si l'on recalcule chaque somme avec une boucle. En flottants, les soustractions répétées peuvent accumuler de petites erreurs d'arrondi.

piège : Faire démarrer la seconde boucle à k − 1. Au premier tour, t[i − k] vaut t[−1], le dernier élément, et Python ne signale rien. On obtient une moyenne de trop, et toutes les moyennes sauf la première sont fausses. Pour [2, 4, 6, 8] et k = 2, on obtient [3.0, 1.0, 3.0, 5.0].

Exercice 10

Approfondissement · Approfondissement, type oral

Renverser une liste sur place, sans créer de nouvelle liste. Expliquer ce que voit l'appelant.

Technique deux indices qui se rapprochent, échange par variable auxiliaire.

Voir le corrigé
  1. Idée : on échange t[i] et t[j] pour i partant de 0 et j partant de n − 1, jusqu'à ce qu'ils se croisent.
  2. Code :
def renverse_sur_place(t):
    i = 0
    j = len(t) - 1
    while i < j:
        aux = t[i]
        t[i] = t[j]
        t[j] = aux
        i = i + 1
        j = j - 1
  1. Terminaison : j − i décroît de 2 à chaque tour. Pour une longueur impaire, l'élément central n'est pas déplacé.
  2. Effet : la fonction ne renvoie rien, mais la liste de l'appelant est modifiée. Après renverse_sur_place(t) avec t = [1, 2, 3], t vaut [3, 2, 1].

piège : Boucler sur tous les i de 0 à n − 1. Chaque paire est échangée deux fois et la liste revient à son état initial.

Erreurs fréquentes

  1. Sortir de la liste : lire t[i + 1] avec i allant jusqu'à len(t) − 1, ou créer un tableau de compteurs trop court d'une case. Toujours vérifier le dernier tour sur une liste de deux éléments.
  2. Mal placer un return : return False dans un else de la boucle, ou return i qui arrête une recherche censée aller jusqu'au bout.
  3. Initialiser un extremum à 0 au lieu du premier élément, ou oublier, dans un second maximum, que l'ancien maximum devient le second.
  4. Oublier que le texte lu est une chaîne : pas de conversion, pas d'en-tête sauté, virgule décimale non remplacée, fin de ligne oubliée à l'écriture, fichier non fermé.
  5. Confondre alias et copie, ou utiliser max, sum, index ou sort là où l'énoncé demande d'écrire la boucle soi-même.

Fiche de cours rédigée à partir du programme officiel d'informatique de BCPST (2021), programme du semestre 1. Elle couvre deux thèmes du tableau du premier semestre. D'abord « Algorithme opérant par boucles dans un tableau unidimensionnel », avec les activités proposées par le programme : recherche d'un élément, recherche du maximum et du second maximum, manipulations élémentaires d'un tableau unidimensionnel, indexation et extraction. Ensuite « Lecture et écriture dans un fichier texte ». Elle s'appuie sur l'annexe « Langage Python », qui liste limitativement les éléments exigibles. Cette annexe ne mentionne pas les fonctions de manipulation de fichiers, qui doivent donc être accompagnées de leur documentation dans un sujet. L'ordre des thèmes du semestre n'est pas imposé, et aucune connaissance relative aux modules n'est exigible.