Revue de code publique


En faisant ma revue quotidienne, je suis tombé sur un code Python assez tarabiscoté que twitter s’est empressé de massacrer.

Néanmoins je ne pense pas que mettre l’affiche à l’auteur soit le meilleur moyen de l’amener à passer plus de temps à coder en Python et à participer à la communauté.

En effet, il me semble qu’il est prof de bio (si j’ai bien suivi l’histo), et personnellement, si on devait me demander de faire un schéma d’une méiose, je pense que le résultat serait du même niveau.

D’un autre côté laisser circuler une code dans ce format, surtout si il a une vocation pédagogique serait contre productif. Améliorons donc ce code pas à pas en justifiant des modifications.

D’abord, voici la fonction en question :

def tripositions(Ligne): #Tri a bulle ?
    l = [k for k in range(len(Ligne))]
    q = [Ligne, l]
 
    for i in range(len(Ligne)-1):
        for j in range(len(Ligne)-i-1):
            if q[0][j]>q[0][j+1]:
                for k in range(2):
                    q[k][j], q[k][j+1]=q[k][j+1], q[k][j]
 
    while q[0][0]==0:
        q=[[q[i][j] for j in range(1, len(q[0]))] for i in range(2)]
 
    return(q)

Elle effectue un bubble sort (un tri des éléments en faisant remonter les plus grands en fin de liste, comme des bulles), puis retire les zéros, et retourne deux ensembles : une liste des éléments triés, et une liste de la position des ces éléments dans la liste originale. Par exemple:

>>> tripositions([4, 0, 9, 0,  5, 17, 7])
[[4, 5, 7, 9, 17], [0, 4, 6, 2, 5]]

D’abord, essayons de comprendre le fonctionnement de l’algo :

def tripositions(Ligne): #Tri a bulle ?
 
    # Ici on récupère une liste [0, 1, 2, 3...] qui contient les positions
    # des éléments dans la liste passée en paramètre
    l = [k for k in range(len(Ligne))]
    # Là on groupe la liste initiale et les positions en une liste
    q = [Ligne, l]
 
    # On fait un tour de boucle pour chaque élément de la liste
    for i in range(len(Ligne)-1):
        # Et à chaque tout de boucle, on refait une boucle qui fait elle-même
        # un tour de boucle pour tous les éléments de la liste, sauf le dernier
        for j in range(len(Ligne)-i-1):
            # Si l'élément de la liste à la position actuelle est plus grand
            # que l'élément de la liste à la prochaine position
            if q[0][j]>q[0][j+1]:
                # On change la place de cet élément dans la liste initiale,
                # la place de la position de cet élément dans la liste des
                # positions
                for k in range(2):
                    q[k][j], q[k][j+1]=q[k][j+1], q[k][j]
 
    # Maintenant que tout est trié, si il y a des zéro, il sont tous en début
    # de liste. Tant qu'il y a un zéro en début de liste, on retire le premier
    # élément de la liste initiale et de la liste des positions.
    while q[0][0]==0:
        q=[[q[i][j] for j in range(1, len(q[0]))] for i in range(2)]
 
    return(q)

Le style d’écriture en Python étant codifié par un document appelé le PEP8, reformatons le code afin qu’il soit plus facile à lire :

# Les noms des variables ne commencent pas par une majuscule. On réservera
# les majuscules aux noms de classe.
# On donne aussi un nom un peu plus explicite à la fonction
def elements_tries_et_positions(ligne):
    """ Tri a bulle d'une liste retournant les valeurs triés et leurs positions"""
    # On met une doctstring pour documenter la fonction plutôt qu'un commentaire
    # On pourra ainsi appeler help(elements_tries_et_positions)
 
    l = [k for k in range(len(ligne))]
    q = [ligne, l]
 
    # On aère un peu le code en rajoutant des espaces autour des signes = et <
    for i in range(len(ligne)-1):
        for j in range(len(ligne)-i-1):
            if q[0][j] > q[0][j+1]:
                for k in range(2):
                    q[k][j], q[k][j+1] = q[k][j+1], q[k][j]
 
    while q[0][0] == 0:
        q = [[q[i][j] for j in range(1, len(q[0]))] for i in range(2)]
 
    # "return" est un mot clé, pas une fonction, et n'a pas besoin de
    # parenthèses
    return q

Le code utilise massivement des ranges() et len(), ce qui me laisse à penser que notre auteur a voulu reproduire un schéma qu’il a connu lors d’une expérience dans un autre langage tel que le C ou le Fortran. C’est très courant quand on passe à une nouvelle technologie, car on essaye de trouver ses repères.

Premier passage pour retirer quelques appels qui ne sont pas idiomatiques :

def elements_tries_et_positions(ligne):
    """ Tri a bulle d'une liste retournant les valeurs triés et leurs positions"""
    # range() va déjà retourner un itérable contenant les valeurs voulues donc
    # pas besoin de faire une liste en intension dessus
    q = [ligne, range(len(ligne))]
 
    # Il n'y a pas d'avantage de performance à itérer sur range() plutôt que
    # sur "ligne" directement : c'est toujours une itération 
    # de même taille et seul le nombre de tours nous intéresse
    # puisqu'on utilise pas l'index dans notre code
    for e1 in ligne:
        # enumerate() retourne automatiquement un itérable [(index, element),
        # (index, element), ...], et permet de se passer du calcule manuel
        # de la position des éléments
        # ligne[:-1] est ce qu'on appelle un slicing, qui nous permet de
        # créer une copie de la liste sans le dernier élément.
        for j, e2 in enumerate(ligne[:-1]):
            # q[0] est la même chose que "ligne", donc pas besoin de faire
            # un indexing.
            if ligne[j] > ligne[j+1]:
                for k in (0, 1):
                    q[k][j], q[k][j+1] = q[k][j+1], q[k][j]
 
    # Même chose puisque q[0] == ligne
    while ligne[0] == 0:
        q = [[q[i][j] for j in range(1, len(q[0]))] for i in range(2)]
 
    return q

Le fait de manipuler la liste initiale et la liste d’indexes groupés dans une 3eme liste rend le code plus difficile à lire. Nous allons éviter ça en créant la liste finale à la fin.

def elements_tries_et_positions(ligne):
    """ Tri a bulle d'une liste retournant les valeurs triés et leurs positions"""
 
    # On supprime la création de la liste finale et on manipule
    # deux variables pendant toute la fonction ce qui rend plus clair sur
    # quelle partie des données on travaille.
    positions = range(len(ligne))
 
    for e1 in ligne:
        for j, e2 in enumerate(ligne[:-1]):
            # On a déjà e2 gratuitement, pas besoin d'utiliser un index
            if e2 > ligne[j+1]:
                # Ici, on manipules les listes par leur nom, c'est
                # plus facile à comprendre que des indexes
                ligne[j], ligne[j+1] = ligne[j+1], ligne[j]
                positions[j], positions[j+1] = positions[j+1], positions[j]
 
    # Si ligne[0] == 0, alors bool(ligne(0)) == False. Python n'a pas besoin
    # de la comparaison numérique dans une condition, juste du contexte booléen.
    # En l'écrivant ainsi, on se rapproche du langage parlé.
    while not ligne[0]:
        # Encore une fois, inutile de grouper les deux variables dans une
        # liste. Les séparer rend l'action plus lisible.
        ligne = [ligne[i] for i in range(1, len(ligne))]
        positions = [positions[i] for i in range(1, len(ligne))]
 
    # On retourne les deux variables sous forme de liste à la fin
    return [ligne, positions]

Enfin, et meme si les listes en intension sont une fonctionalité du langage qu’il faut toujours garder sous le code, ce sont des boucles et il est bon de se passer de boucles non nécessaires. A l’inverse, la compatibilité avec Python 3 va nous obliger à rajouter une boucle (implicite) de plus.

def elements_tries_et_positions(ligne):
    """ Tri a bulle d'une liste retournant les valeurs triés et leurs positions"""
 
    # range() retourne un générateur en Python 3, il faut donc le convertir
    # en liste pour que ça marche dans les 2 versions
    positions = list(range(len(ligne)))
 
    for e1 in ligne:
        for j, e2 in enumerate(ligne[:-1]):
            if e2 > ligne[j+1]:
                ligne[j], ligne[j+1] = ligne[j+1], ligne[j]
                positions[j], positions[j+1] = positions[j+1], positions[j]
 
    while not ligne[0]:
        # Il s'agit juste de retirer tous les éléments nuls de gauche, il
        # n'est pas nécessaire de parcourir toute la liste pour ça : pop(0)
        # retire l'élément en début de liste
        ligne.pop(0)
        positions.pop(0)
 
    return [ligne, positions]

Rajoutons des commentaires techniques, car en première lecture, ce n’est pas évident de savoir quel bloc fait quoi (le retrait des zéros m’a demandé un certains temps avant que je le pige) :

def elements_tries_et_positions(ligne):
    """ Tri a bulle d'une liste retournant les valeurs triés et leurs positions"""
 
    # Liste des positions initiales des éléments
    positions = list(range(len(ligne)))
 
    # Bubble sort qui traite en parallèle les valeurs et leurs positions
    for e1 in ligne:
        for j, e2 in enumerate(ligne[:-1]):
            if e2 > ligne[j+1]:
                ligne[j], ligne[j+1] = ligne[j+1], ligne[j]
                positions[j], positions[j+1] = positions[j+1], positions[j]
 
    # Retrait des zéro (qui après le tri sont tous en début de liste)
    while not ligne[0]:
        ligne.pop(0)
        positions.pop(0)
 
    return [ligne, positions]

En tout nous avons fait plusieurs choses, qui sont presque toujours les mêmes étapes pour toute revue de code :

  • Formater le code pour qu’il soit plus lisible.
  • Retirer des opérations non nécessaires.
  • Utiliser des noms plus explicites pour nos conteneurs.
  • Utiliser les fonctions déjà proposées par Python plutôt que faire les choses manuellement.

Python est un langage de haut niveau, qui favorise la productivité et met l’accent sur la lisibilité. Aussi il est bon de tirer parti le maximum du langage pour avoir le code le plus facile à lire possible. Ne pas hésiter à avoir des noms longs, des opérations sur deux lignes, des usages d’outils tout faits, etc.

J’imagine que le but de l’exercice était de faire coder un tri à bulle, donc j’ai modifié le code précédent. Néanmoins Python propose déjà des fonctions qui font tout ça automatiquement, et voici ce pourrait donner la fonction si on devait l’écrire en tirant parti du toute la puissance du langage :

# On met tout en anglais et on change le nom de paramètre pour montrer
# qu'on accepte n'importe quel itérable.
# On rajoute aussi deux paramètres avec des valeurs saines par défaut mais
# qui permettent d'orienter le tri différemment si on le souhaite.
def get_sorted_items_and_positions(iterable, key=lambda x: x[1], reverse=False):
    """ Sort an iterable and return a tuple (sorted_items, old_positions)"""
 
    # On récupère la liste de tous les éléments non nuls et leur position
    indexed_non_null_elements = [(i, e) for i, e in enumerate(iterable) if e]
    # On les tri avec un tim sort plutôt qu'un bubble sort
    indexed_non_null_elements.sort(key=key, reverse=reverse)
    # On sépare les indexes des élements avec une astuce de sioux
    indexes, elements = zip(*indexed_non_null_elements)
 
    # On retourne généralement un tuple quand on a peu de valeurs à retourner
    return elements, indexes

La fonction va alors retourner un tuple de tuple au lieu d’une liste de liste, mais sinon fera les mêmes opérations :

>>> get_sorted_items_and_positions([4, 0, 9, 0,  5, 17, 7])
((4, 5, 7, 9, 17), (0, 4, 6, 2, 5))

Néanmoins elle aura le double avantage de fonctionner avec n’importe quel itérable (get_sorted_items_and_positions acceptera alors en paramètre une liste, mais aussi un tuple, un set ou même un fichier) mais aussi d’être beaucoup plus rapide puisque sort() est codé en C sous le capot.

Comme ai-je pondu cette fonction ?

D’abord, j’ai observé l’algorithme, et j’ai noté que les zéros étaient retirés. Il est souvent plus facile de filtrer ses données avant le traitement, donc je retire les 0 tout au début. Python est un fantastique langage pour tout ce qui est filtre, et on peut non seulement le faire en une ligne, mais indexer les éléments restant dans la foulée.

Ensuite, j’utilise la fonction de tri intégrée au langage, qui est un algo non seulement plus efficace que le bubble sort mais en prime est codé en C par les auteurs de Python, donc qui sera beaucoup plus performant. La manière dont fonctionne le tri en Python permet de trier mes paires (position, valeur) comme un seul élément, mais en fonction de la valeur uniquement. Un autre avantage, c’est que cette fonction permet de changer le type de tri (par exemple trier du plus grand au plus petit), et donc je peux exposer ces paramètres supplémentaires dans ma fonction pour laisser plus de control à l’utilisateur. Grâce aux valeurs par défaut, ce contrôle supplémentaire ne rend pas l’usage plus complexe, puisque l’utilisateur peu juste ignorer ces paramètres.

Enfin, j’utilise une combinaison de slicing, d’unpacking (via l’opéateur splat l’opérateur splat) et une particularité de la fonction zip() qui permet de transposer un itérable de paires en deux itérables d’éléments seuls :

>>> paires = [(1, "a"), (2, "b"), (3, "c")]
>>> zip(*paires)
[(1, 2, 3), ('a', 'b', 'c')]
>>> nombres, lettres = zip(*paires)
>>> nombres
(1, 2, 3)
>>> lettres
('a', 'b', 'c')

Notez bien que personne ne demande à quelqu’un qui commence à toucher du Python de trouver ce code. C’est juste comme ça qu’on finit par le faire, à l’usage.

18 thoughts on “Revue de code publique

  • Furankun

    Merci pour le décorticage et le très bon exemple, et merci pour l’astuce avec zip.

    J’ai un peu de mal à comprendre comment tu peux passer de

    for j in range(len(ligne)-i-1):

    à

    for j, e2 in enumerate(ligne[:-1]):

    est-ce que tu pourrais détailler cette partie? ce que je ne capte pas c’est comment tu peux ne plus prendre en compte i/e1.

  • cym13

    Quand tu écris dans ton dernier bout de code:

    indexed_non_null_elements = [(i, e) for i, e in enumerate(iterable) if e]

    je ne suis pas certain qu’un débutant identifie le “if e” comme le filtrage des éléments nuls, je propose soit de le changer en “if e != 0” (mais plus long) soit de faire un commentaire plus explicite.

  • Sam Post author

    @Furankun

    La première boucle est là uniquement pour faire un nombre de tours égal au nombre d’éléments : on ne l’utilise pas pour accéder aux éléments. Donc pas besoin de compteur dedans car une boucle for sur une liste fait exactement un nombre de tours égale aux nombre d’élément dans la liste. Le i est ce fameux compteur issu de la première boucle dans l’ancienne version, et nous le supprimons.

    Du coup seule la deuxième boucle compte, qui est répétée pour chaque tour de la première boucle. Dans cette seconde boucle, on veut accéder à un compteur (car c’est nécessaire pour échanger la place des éléments dans la liste). Grace à ligne[:-1] on accède à la liste sans le dernier élément, ce qui nous donne nous seulement le compteur voulu en le passant à enumerate(), mais nous permet aussi d’avoir un des deux élément à comparer sans avoir à utiliser l’index. La syntaxe j, e2 est du sucre syntaxique appelé ‘unpacking’ – dans l’article je fais un lien vers un tuto sur l’unpacking si besoin – qui permet de bien distinguer la variable qui va contenir le compteur, et celle qui va contenir l’élement de la liste en cours.

    @cym13:

    Le dernier exemple n’est pas un exemple pour débutant, il est explicitement une démonstration d’une version pour codeurs de métiers, lesquels reconnaitrons cet idiome courant en Python.

    Quand on est pas informaticien professionnel, le plus important est que le code marche. En cela, la toute première version faisait son travail et l’améliorer est un exercice de style ou pédagogique. Quand on code tous les jours en revanche, utiliser le style fait pour le langage qu’on utilise est primordial : l’expressivité amène à la productivité.

    En l’occurrence, en Python tout objet à une valeur dans un contexte booléan (qu’on peut obtenir en faisant bool() dessus), et la règle est simple : tout est vrai sauf False, None, 0 et les structures de données vides (“”, [], {}, (), set(), etc).

    Cela ne dispense bien entendu pas de l’utilisation d’un nom explicite qui signale le retrait des zéros, “indexed_non_null_elements”, les noms étant une forme de documentation de code. Car comme tu le signales, on ne voit pas du premier coup d’oeil qu’on vise spécifiquement les zéro. Dans une expression plus complexe, il est en effet parfois nécessaire de préciser != 0 afin de faciliter la lecture rapide du code. Sur un code de 3 lignes, les indices suffisent généralement.

  • betepoilue

    Génial comme toujours, merci pour les astuces et franchement chapeau d’arriver à la refactorer aussi proprement. Finalement ce serait super intéréssant si les gens qui critiquaient avaient posté leur propre révision du code histoire de pouvoir comparer.

    Deux petites typos relevées : dans le 5ème bloc de code : liste en intention plutôt que liste en intensions, et dans le bloc “Formatté le code pour qu’il soit plus lisible.

    Retiré des opérations non nécessaires.

    Utilisé des noms plus explicites pour nos conteneurs.

    Utilisé les fonctions déjà proposées par Python plutôt que faire les choses manuellement.” => passer à l’impératif “Formatter, Retirer, Utiliser, Utiliser” c’est tout ;)

  • Sam Post author

    Il s’agit bien des listes en intension, car je ne parle pas d’une intention de l’esprit, mais d’une intension mathématique (https://fr.wikipedia.org/wiki/Intension_et_extension).

    Quand à la deuxième, je vous dire “[nous avons] … formatté”, “[nous avons]… retiré”, mais en l’écrivant c’est vrai que j’ai bien senti que l’impératif passerait mieux. Je vais changer ça.

  • Ryzz

    @Sam Pour la remarque de Furankun:

    dans le premier cas, à chaque boucle sur i, la longueur de la boucle sur j change.

    Dans le deuxième cas, tu fais toujours la même boucle (d’ailleurs, ce ne serait pas un cas d’utilisation du module itertools?)

    Pour moi non plus ce n’est pas la même chose…

  • Sam Post author

    La longueur de la boucle ne change pas, car la taille de la liste ne change pas. A aucun moment on ne retire un élément de la liste. On inverse la place de deux éléments, la liste reste donc à la même taille.

    Tu as raison de dire qu’on ne fait pas la même chose. En effet, dans la deuxième version, on créé une copie de la liste à chaque tour de boucle, ce qui a une pénalité de mémoire que la première n’avait pas. Mais on récupère des performances sur toutes les autres modifications, donc j’en déduits que ce n’est pas l’objectif principal de l’auteur et qu’on reste sur une échelle acceptable vu la lisibilité gagnée.

    Par ailleurs, oui, ce serait une excellente opportunité pour utiliser itertools, ou faire un générateur qui parcours par fenêtre avec un deque, mais je voulais rester dans le même style de programmation pour la transformation du code histoire que ça ait une valeur pédagogique. La version finale, elle n’est pas celle contrainte, mais itertools n’est pas utile dedans donc je ne l’ai pas utilisé.

  • Seb

    Evidemment, le premier code proposé n’est pas un super exemple à montrer à des élèves. Mais d’un autre côté, si le but est d’apprendre aux élèves à coder, est-ce une bonne chose de faire de “l’idiomatic Python” ?

    Python propose en effet pleins de fonctionnalités qui sont intéressantes à programmer par un débutant, comme par exemple retirer le premier élément d’une liste. Utiliser ces fonctionnalités masque un peu la complexité, en mettant par exemple au même niveau un pop et un sort. Et il me semble qu’il important de comprendre profondément ce que fait l’ordinateur, et je pense que le meilleur moyen pour cela est d’aller trifouiller les indices. Mais du coup, c’est sûr qu’on n’a pas un code Pythonique.

    C’est pour ça que je suis finalement partagé sur l’utilisation de Python en tant que premier langage (alors que j’étais très enthousiaste en début d’année) :

    soit on fait du code pythonique et on masque les difficultés algorithmiques aux élèves, ce qui ne leur permet pas de s’exercer sur des manipulations simples du genre spliting, slicing, poping,…
    soit on utilise massivement les indices et les boucles et du coup, on leur enseigne une “mauvaise” façon de faire du Python.

    Quel est votre avis là dessus ?

  • Sam Post author

    Et bien, cela dépend de l’objectif. Mon objectif n’est pas d’enseigner l’algorithmie.

    Il y a 20 ans, cela avait du sens car les langages étaient proches de la machine, et les machines peu puissantes. Il fallait se créer des modèles mentaux très complexes pour tirer parti de son environnement. Aujourd’hui gaspiller 200 Mo de RAM et 1Go pour économiser du temps homme n’est pas rare et les langages de haut niveau ont pour but de masquer la complexité algorithmique.

    Ca ne plait pas aux programmeur car on a en face des gens qui programment… sans programmer.

    En vérité c’est juste qu’il y a de nouveaux types de profiles de dev, tout aussi utiles, et devenant majoritaires, pour qui ce type de programmation est plus productive. Ils n’auront jamais besoin de savoir coder un bubble sort, ni même que ça existe, et pourront être experts dans leur domaine.

    On en revient donc à l’objectif. Pour moi il est celui de permettre à la personne de faire la tâche qu’elle veut effectuer dans un temps qui la satisfait, avec une exécution du programme à la vitesse qui la satisfait, avec les fonctionnalités qui la satisfait. Pour la plupart des gens que je forme : developpeurs Web, géographes, biologistes, sysadmin, hommes à tout faire electroniques et étudiants en prépa, rester très haut niveau atteint cet objectif.

    Je vois les ravages de l’inverse : des gens formés à penser en C ou Java, qui codent denses, peu lisibles, avec des optimisations prémturarés et qui mènent souvent à une démotivation car trop long à produire.

    Donc pour ma part, j’apprécie énormément Python pour introduire à la programmation, surtout si la personne n’a pas pour vocation de devenir un développeur pro.

    Si en revanche il l’a, travailler sur d’autres langages en complément est bénéfique. Compléter la formation avec un langage bas niveau (C ou Rust) et un langage fonctionnel (Lisp ou dans une moindre mesure, JS) me semble important, justement pour former à la rigueur, qui ne doit pas être une priorité, mais qui aide sur le long terme.

    Cela ne doit néanmoins pas se faire en priorité : le plaisir de coder, la productivité, la lisibilité du code, la documentation, les tests, le travail collaboratif, l’autonomie… Toutes ces choses sont plus importantes que d’apprendre des algo et gérer les perfs. La logique viendra avec la pratique, les bonnes pratiques pas forcément.

  • thib

    Salut Sam, mes scripts ressemblent en général à l’exemple de départ pour les raisons que tu cites : pas mon job, et des habitudes héritées d’autres langages. Je vais pouvoir leur appliquer le même traitement pour les rendre plus élégants, et avoir moins honte de les partager.

    Vraiment parfait cet article, merci !

  • toub

    J’ai pas encore lu le détail de l’article, mais ma 1ère réaction à chaud, c’est d’abord un merci de poster sur twitter autre chose qu’un flinguage en règle ! Le défouloir sur un gars qu’on colle au poteau parce que -sacrilège ! – il code pas pythonic, ça défoule mais ça devient fatigant à la longue.

    Je préfère nettement la démarche de proposer et d’expliquer une amélioration, ça me parait plus constructif – comment ça j’enfonce des portes ouvertes ?

  • Vazywilly

    Merci, ce genre d’explication pas à pas c’est génial pour les bleu-bites comme moi

  • Seb

    Re Sam (oue, boulet comme je suis, j’ai pas coché la case “follow this fucking comments”, du coup j’avais pas vu ta réponse),

    Merci pour ta réponse détaillée.

    Je suis d’accord avec ta notion d’objectif, et qu’aujourd’hui, une grande partie des gens développent sans avoir réellement besoin de savoir faire un “sort” ; cependant, pour le cas des élèves en prépa, l’un des buts avoués du programme est quand même de faire de l’algorithmique, notamment du tri (et pas que du bubble, le quick et le merge sont aussi de la partie). Pour ces étudiants là, est-il judicieux de faire code pythonique ? De faire du python ?

  • Sam Post author

    Si l’algo est important, alors autant se pencher sur des algos métiers. Des trucs pratiques, des cas concrets, pas résoudre des problèmes dont la solution la plus pratique existe depuis toujours. Sinon on peut aussi implémenter son compilateur :) Par exemple, des algo décisionnels ou prévisionnels, analyse de tendances (prix, indices, meteo, etc), des algo pour organiser un process industriel, optimiser des réseaux (canalisation, groupes sociaux, cables, etc)… En plus ça parlent plus aux gens, parce que ça utilise des données concrètes derrière. Et tous les trucs ennuyeux comme le tri, Python s’en charge, les élèves peuvent se concentrer sur le problème à résoudre et non à fabriquer l’outil pour résoudre le problème : c’est plus gratifiant, et surtout plus proche de la réalité du terrain. En informatique, on fait 90% de plomberie.

  • Martin

    Salut Sam, je viens déterrer le post pour apporter des explications : je suis dans la filière (prépas BCPST) où on enseigne cette me*de d’algorithme que tu as déterré. Jusqu’à récemment, c’était une option : résultat peu de gens compétents. Mais avec le changement de programme, l’info est devenue obligatoire, du coup toutes les prépas BCPST ont dû se plonger dans Python (et migrer, car avant l’option info était en MatLab…).

    Le programme (de trucs à enseigner) est fait pour des gens qui, déjà, ne sont pas bons en maths, et n’y connaissent rien à l’info. En plus, il stipule qu’il faut connaître des “algorithmes caractéristiques” et savoir les recoder à la main, ce qui est complètement con : style Dijkstra ou Tri à Bulles, justement. A l’épreuve, les examinateurs peuvent potentiellement demander un script pour “trouver le maximum d’une liste”. C’est de ce niveau là.

    A titre d’exemple, c’est déjà une hérésie que j’aie codé mon projet en POO, donc il ne faut pas trop en attendre d’un prof de bio. Surtout que le but était vraiment de recoder la fonction, pas de s’en servir. Après… ta solution est très élégante ^_^

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.