Mon premier projet Python: comment j'ai converti un fichier texte désorganisé en un fichier CSV soigné

J'ai donc décidé d'apprendre Python. Il s'avère que ce langage de programmation informatique n'est pas si difficile (enfin, jusqu'à ce que j'aie ce projet!: P).

En quelques secondes, je suis tombé amoureux de sa syntaxe simple et nette et de son indentation automatique lors de l'écriture. J'ai été fasciné quand j'ai appris que les structures de données comme les listes, les tuples et le dictionnaire pouvaient être créées et initialisées dynamiquement avec une seule ligne (comme ça, list-name = []).

De plus, les valeurs contenues dans ceux-ci peuvent être consultées avec et sans l'utilisation d'index. Cela rend le code très lisible car l'index est remplacé par un mot anglais de son choix.

Eh bien, assez parlé de la langue. Laissez-moi vous montrer ce que le projet exigeait.

Mon frère m'a donné ce projet. Il est tombé sur un fichier texte contenant des milliers de mots. Beaucoup de mots partageaient presque le même sens. Chaque mot avait sa définition et une phrase d'exemple à côté, mais d'une manière pas si organisée. Il y avait des espaces et des retours à la ligne entre un mot et sa phrase. Certains aspects manquaient aux mots. Voici les extraits du fichier texte dont je parle:

Il voulait que les aspects du texte soient uniformes. Pour cela, il avait besoin de moi pour assortir parfaitement tous les mots de sens similaires à côté d'un sujet. Il m'a dit que cela pouvait être réalisé en capturant toutes les données du texte dans un dictionnaire au format suivant:

puis les écrire dans un fichier CSV (valeurs séparées par des virgules).

Il m'a demandé si je pouvais prendre cela comme mon premier projet, maintenant que j'avais appris les principes fondamentaux. J'étais ravi de travailler sur la logique et j'ai donc immédiatement accepté. Interrogé sur la date limite, il m'a donné un temps décent de 2 jours pour terminer.

Hélas, j'ai fini par prendre le double de temps car j'ai eu du mal à déboguer correctement le code écrit. Franchement, s'il n'y avait pas eu les courtes visites de mon frère dans ma chambre pour regarder les progrès et faire allusion aux mauvaises hypothèses faites par moi en écrivant les conditions, j'étais destiné à terminer le projet dans l'éternité: P

J'ai commencé par créer des mini-tâches dans le programme que je cherchais à terminer avant de construire l'ensemble du programme. Celles-ci étaient énumérées ci-dessous:

1. Former un Regex pour faire correspondre un nombre et le mot à côté.

J'ai examiné le fichier texte et j'ai remarqué que chaque sujet (appelé ici «clé») avait un numéro qui le précédait. Donc, j'ai écrit quelques lignes de code pour créer une regex (expression régulière - un outil puissant pour extraire du texte) du modèle comme suit:

Cependant, lorsque j'ai exécuté cela, j'ai eu une erreur, UnicodeDecodeError, pour être exact, ce qui signifiait que je n'avais pas accès au fichier texte. Je l'ai recherché sur //stackoverflow.com et après une longue recherche sans succès, mon frère est venu et a trouvé une solution. L'erreur a été rectifiée comme suit:

Pourtant, je n'ai pas obtenu le résultat souhaité. C'était parce que certaines clés avaient des barres obliques ('/') ou des espaces ('') dans le texte que mon expression régulière ne pouvait pas correspondre. J'ai pensé à améliorer l'expression regex plus tard et j'ai donc écrit un commentaire à côté.

2. Obtention d'une liste de lignes sous forme de chaînes à partir du fichier texte

Pour cela, j'ai écrit seulement 1 ligne de code et heureusement, aucune erreur ne s'est produite.

Cependant, j'ai obtenu une liste impure. Il contenait des retours à la ligne ('\ n') et des espaces ('') J'ai ensuite cherché à affiner la liste comme suit:

3. Extraire des mots, des significations et des exemples de phrases séparément et les ajouter aux listes correspondantes.

C'était de loin la partie la plus difficile à faire car elle impliquait une logique appropriée et un jugement approprié par reconnaissance de formes.

Fait intéressant, en regardant le fichier texte, j'ai remarqué plus de modèles. Chaque mot avait sa signification dans la même ligne séparée par un signe «=». De plus, chaque exemple était précédé du signe ":" et du mot clé "Exemple".

J'ai pensé à utiliser à nouveau regex. J'ai trouvé une solution alternative et plus élégante en découpant la ligne (maintenant une chaîne dans la liste) en fonction de l'emplacement des symboles. Le découpage est une autre fonctionnalité intéressante de python. J'ai écrit le code comme suit:

Le code ci-dessus se lit presque comme l'anglais. Pour chaque ligne de la liste propre, il vérifie si elle a un signe «=» ou «:». Si c'est le cas, l'index du signe est trouvé et le découpage est effectué en conséquence.

Dans le premier «si», la partie avant le «=» est stockée dans la variable «mot» et la partie après elle est stockée dans «signification». De même pour le second 'if' ('elif - else if - dans ce cas), la partie après': 'est stockée dans' example '. Et après chaque itération, le mot, la signification et la phrase d'exemple sont stockés dans les listes correspondantes. De cette manière, toutes les données peuvent être extraites.

Jusqu'ici tout va bien. Mais, j'ai noté que l'extraction devait être faite d'une manière telle que chaque mot (et ses aspects) de la clé particulière devaient être accumulés ensemble comme une valeur pour la clé. Cela signifiait qu'il était nécessaire de stocker chaque mot, signification et exemple dans un tuple. Chaque tuple devait être stocké dans une seule liste qui se représenterait comme la valeur d'une clé particulière. Ceci est illustré ci-dessous:

Pour cela, j'ai prévu de rassembler chaque mot, sens et phrase de chaque clé dans une liste séparée entourée d'une autre liste, disons key-list. Encore une fois, l'image vous dira plus précisément:

Pour ce faire, j'ai ajouté le code suivant à celui que j'ai écrit pour le découpage:

La logique de ce code (la partie else) s'est avérée fausse, malheureusement. J'ai supposé à tort que seulement 2 conditions ('=' et ':') existaient dans le texte. Il y a eu de nombreuses exceptions que je n'ai pas remarquées. J'ai fini par perdre des heures à déboguer d'éventuelles erreurs dans la logique. J'avais supposé que le fichier texte complet suivait le même schéma. Mais ce n'était tout simplement pas le cas.

Incapable de progresser, je suis passé à la partie suivante du programme. J'ai pensé que je pourrais utiliser l'aide de mon frère après avoir terminé les autres parties. : P

À suivre…

4. Création de valeurs pour les clés à l'aide de la fonction Zip et du déballage des paramètres.

À ce stade, je n'étais pas tout à fait sûr de ce que je ferais même après avoir réalisé la configuration de listes ci-dessus. J'avais appris la fonction 'Zip' et le 'Parameter Unpacking' lors d'une des discussions techniques de mon frère, qui a littéralement compressé les listes qui lui étaient transmises, comme ceci:

J'ai donc pensé que je pourrais en quelque sorte combiner ces deux caractéristiques pour obtenir le résultat souhaité. Après un peu de va-et-vient, de tester les fonctionnalités et de travailler sur des listes factices, j'ai réussi. J'ai créé un fichier séparé (bêta) pour cette tâche, dont l'extrait est donné ci-dessous:

Le fonctionnement du code ci-dessus peut être compris en jetant un œil à la sortie:

La fonction zip () zippe les listes ou valeurs correspondantes dans les listes et les enferme dans un tuple. Les tuples à l'intérieur des listes sont ensuite convertis en listes pour décompresser et compresser davantage. Enfin, le rendement souhaité est obtenu.

Je me suis senti très soulagé que le code fonctionne cette fois. J'étais heureux de pouvoir manipuler les données extraites et de les mouler dans le format requis. J'ai copié le code dans le fichier principal sur lequel je travaillais et modifié les noms des variables en conséquence. Il ne restait plus qu'à attribuer des valeurs aux clés du dictionnaire (et bien sûr à la partie extraction!).

5. Attribution de valeurs aux clés du dictionnaire.

Pour cela, je suis venu à cette solution après quelques expérimentations avec le code:

Cela a produit le résultat souhaité comme suit:

Le programme était presque terminé. Le principal problème résidait dans la partie extraction des données.

… Suite de la section 3

Après des heures et des heures de débogage, je suis devenu de plus en plus frustré de savoir pourquoi cette fichue chose ne fonctionnait pas. J'ai appelé mon frère et il m'a donné un indice subtil sur les hypothèses que j'avais faites lors de la définition des boucles conditionnelles et des clauses if-else. Nous avons examiné le fichier texte et avons remarqué que certains mots avaient des exemples sur deux lignes au lieu d'une.

Selon ma logique de code, puisqu'il n'y a pas de signe «:» dans la deuxième ligne (ni de signe «=», d'ailleurs), le contenu de la ligne ne serait pas traité comme faisant partie de l'exemple. En conséquence, cette instruction rendrait la dernière partie «else» vraie et exécuterait le code qui y est écrit. Compte tenu de tout cela, j'ai modifié le code comme ci-dessous:

Ici, hasNumbers () est une fonction qui vérifie si une ligne donnée contient des nombres. Je l'ai défini comme suit:

Cela permet de collecter la deuxième ligne de l'exemple si toutes les autres conditions échouent, de la combiner avec la première ligne, puis de l'ajouter à la liste correspondante comme précédemment.

À ma grande déception, cela n'a pas fonctionné et a plutôt montré une erreur indiquant que l'index était hors de portée. J'étais stupéfait, car chaque ligne de code semblait logiquement correcte à mon avis.

Après des heures de folie, mon frère m'a montré un moyen de récupérer les numéros de ligne où l'erreur s'est produite. L'une des principales compétences en programmation est la capacité de déboguer le programme, de vérifier correctement les erreurs possibles et de maintenir un flux continu.

Fait intéressant, l'ajout suivant au code a signalé que l'erreur s'est produite autour de la ligne numéro 1750 du fichier texte.

Cela signifiait que le programme fonctionnait bien jusqu'à ce numéro de ligne et que mon code était correct! Les problèmes résidaient dans mes hypothèses erronées et aussi dans le fichier texte grâce à son hétérogénéité.

Cette fois-ci, j'ai remarqué que certaines clés n'étaient pas par leurs numéros, ce qui posait des problèmes dans le flux logique. J'ai rectifié les erreurs en modifiant davantage le code comme suit:

Cela a bien fonctionné jusqu'à la ligne 4428 du fichier texte mais s'est écrasé juste après. J'ai vérifié ce numéro de ligne sur le fichier texte lui-même mais cela n'a pas beaucoup aidé. Puis j'ai réalisé, à mon grand bonheur, que ce devait être la dernière ligne. L'ensemble du programme a travaillé sur la liste vierge qui était dépourvue de nouvelles lignes et d'espaces. J'ai imprimé la dernière ligne de la liste propre et l'ai comparée à la dernière ligne du fichier texte. Ils correspondaient!

J'étais extrêmement heureux de le savoir car cela signifiait que le programme était exécuté jusqu'à la fin. La seule raison pour laquelle il s'est écrasé était qu'après la dernière phrase, aucun code n'avait de sens. Mes conditionnels ont été conçus pour vérifier à chaque fois la ligne suivante également, ainsi que la ligne actuelle. Comme il n'y avait pas de ligne après la dernière ligne, il s'est écrasé.

J'ai donc écrit une ligne de code supplémentaire pour couvrir cela:

Tout fonctionnait maintenant. Finalement! Il ne me restait plus qu'à attribuer les clés aux valeurs correspondantes et c'est tout! J'ai pris une pause à ce moment, considérant que mon projet était enfin terminé. J'y ajouterais quelques touches finales plus tard.

Mais avant de faire une pause, j'ai décidé d'inclure chaque code dans diverses fonctions afin de rendre le code soigné. J'ai déjà eu beaucoup de mal à naviguer dans les lignes de code. J'ai donc décidé de faire une pause après avoir fait cela.

Cependant, après cela, le programme a commencé à donner des erreurs de portée variable. J'ai réalisé que c'était parce que les variables déclarées à l'intérieur des fonctions ne peuvent pas être appelées directement depuis l'extérieur de la fonction comme elles le sont dans l'espace de noms local. Ne voulant pas faire d'autres changements en raison de cette erreur boiteuse, j'ai décidé de revenir au même code avec lequel je me frappais la tête depuis le début.

Cependant, à ma totale incrédulité, le programme ne fonctionnait pas de la même manière qu'auparavant. En fait, cela n'a pas fonctionné du tout! Je ne pouvais tout simplement pas comprendre la raison (et je ne peux toujours pas!). J'étais complètement déprimé pour le reste de la journée. C'était comme vivre un cauchemar avant même de s'endormir!

Heureusement et miraculeusement, le code a fonctionné le lendemain après avoir apporté quelques modifications minutieuses. Je me suis assuré de créer de nombreux fichiers bêta (pour chaque modification apportée) par la suite afin d'éviter un tel chaos inutile.

Après quelques heures de plus, j'ai pu enfin terminer mon programme (mais pas avant d'avoir consommé 4 jours complets). J'ai apporté quelques modifications supplémentaires telles que:

i) modifier la fonction «hasNumbers» en fonction «hasNumbersDot» et exclure l'expression régulière que j'ai créée plus tôt dans le programme. Cela correspondait plus efficacement aux clés car il n'avait aucune hypothèse et donc aucune exception. Le code pour cela est le suivant:

ii) remplacer la condition regex et le code pour obtenir les clés de la liste propre.

iii) combiner les conditions 'si' dans la partie 'extraction d'exemples'

iv) matérialiser le code pour l'attribution des touches du dictionnaire

De plus, après quelques essais et erreurs, j'ai pu convertir les données obtenues en un fichier CSV magnifiquement structuré:

Vous pouvez consulter mon référentiel github sur mon profil pour afficher le code complet du programme, y compris le fichier texte et le fichier csv.

Globalement c'était une bonne expérience. J'ai beaucoup appris de ce projet. J'ai également gagné en confiance en mes compétences. Malgré quelques événements malheureux (la programmation implique de telles choses: P), j'ai finalement pu terminer la tâche donnée.

Une dernière chose! Récemment, je suis tombé sur un mème hilarant concernant les étapes du débogage qui est si lié à mon expérience que je ne peux pas résister au partage. xD

Merci d'avoir fait tout le chemin jusqu'à ici (même si vous en avez sauté la plupart pour vérifier le résultat final: P).