Mi primer proyecto de Python: cómo convertí un archivo de texto desorganizado en un archivo CSV ordenado

Entonces decidí aprender Python. Resulta que este lenguaje de programación de computadoras no es tan difícil (bueno, ¡hasta que obtuve este proyecto!: P).

En cuestión de segundos, me enamoré de su sintaxis fácil y nítida y su sangría automática al escribir. Me quedé hipnotizado cuando supe que las estructuras de datos como listas, tuplas y diccionarios se pueden crear e inicializar dinámicamente con una sola línea (así, list-name = []).

Además, se podía acceder a los valores contenidos en estos con y sin el uso de índices. Esto hace que el código sea altamente legible ya que el índice se reemplaza por una palabra en inglés de su elección.

Bueno, ya se ha dicho suficiente sobre el idioma. Déjame mostrarte lo que exigía el proyecto.

Mi hermano me dio este proyecto. Se encontró con un archivo de texto que contenía miles de palabras. Muchas de las palabras compartían casi el mismo significado. Cada palabra tenía su definición y una oración de ejemplo al lado, pero de una manera no tan organizada. Había espacios y nuevas líneas entre una palabra y su oración. Faltaban algunos aspectos en las palabras. A continuación se muestran los fragmentos del archivo de texto del que estoy hablando:

Quería que los aspectos del texto fueran uniformes. Para eso, necesitaba que yo clasificara cuidadosamente todas las palabras con significados similares junto a un tema. Me dijo que esto podría lograrse capturando todos los datos del texto en un diccionario en el siguiente formato:

y luego escribirlos en un archivo CSV (valores separados por comas).

Me preguntó si podía tomar esto como mi primer proyecto, ahora que había aprendido los fundamentos. Estaba emocionado de resolver la lógica, así que acepté instantáneamente. Cuando se le preguntó sobre la fecha límite, me dio un tiempo decente de 2 días para terminar.

Por desgracia, terminé tomando el doble de tiempo porque luché para depurar el código escrito correctamente. Francamente, si no hubiera sido por las breves visitas de mi hermano a mi habitación para ver el progreso e insinuar las suposiciones incorrectas que hice mientras escribía las condiciones, estaba destinado a terminar el proyecto en la eternidad: P

Comencé creando mini tareas dentro del programa que buscaba terminar antes de construir el programa completo. Estos fueron los que se enumeran a continuación:

1. Formar una expresión regular para que coincida con un número y la palabra al lado.

Examiné el archivo de texto y noté que cada tema (en este documento denominado "clave") tenía un número que lo precedía. Entonces, escribí algunas líneas de código para hacer una expresión regular (expresión regular, una herramienta poderosa para extraer texto) del patrón de la siguiente manera:

Sin embargo, cuando ejecuté esto, recibí un error, UnicodeDecodeError, para ser exactos, lo que significaba que no tenía acceso al archivo de texto. Lo busqué en //stackoverflow.com y después de una larga búsqueda sin suerte, mi hermano vino y encontró una solución. El error se corrigió de la siguiente manera:

Aún así, no obtuve el resultado deseado. Esto se debió a que algunas teclas tenían barras inclinadas ('/') o espacios ('') en el texto que mi expresión regular no podía coincidir. Pensé en mejorar la expresión de expresiones regulares más tarde y escribí un comentario al lado.

2. Obtener una lista de líneas como cadenas del archivo de texto

Para esto, escribí solo 1 línea de código y, afortunadamente, no aparecieron errores.

Sin embargo, obtuve una lista sucia. Contenía líneas nuevas ('\ n') y espacios ('') Luego busqué refinar la lista de la siguiente manera:

3. Extraer palabras, significados y oraciones de ejemplo por separado y agregarlas a las listas correspondientes.

Esta fue, con mucho, la parte más difícil de hacer, ya que implicaba una lógica adecuada y un juicio adecuado mediante el reconocimiento de patrones.

Curiosamente, mientras miraba el archivo de texto, noté más patrones. Cada palabra tenía su significado en la misma línea separada por un signo '='. Además, cada ejemplo fue precedido por el signo ':' y la palabra clave 'Ejemplo'.

Pensé en hacer uso de regex nuevamente. Encontré una solución alternativa y más elegante cortando la línea (ahora una cadena en la lista) de acuerdo con la ubicación de los símbolos. Cortar es otra característica interesante de Python. Escribí el código de la siguiente manera:

El código anterior se lee casi como en inglés. Para cada línea en la lista limpia, verifica si tiene un signo '=' o ':'. Si es así, entonces se encuentra el índice del signo y el corte se realiza en consecuencia.

En el primer 'si', la parte anterior al '=' se almacena en la variable 'palabra' y la parte posterior se almacena en 'significado'. De manera similar, para el segundo 'if' ('elif - else if - en este caso), la parte después de': 'se almacena en' example '. Y después de cada iteración, la palabra, el significado y la oración de ejemplo se almacenan en las listas correspondientes. De esta forma, se pueden extraer todos los datos.

Hasta aquí todo bien. Pero, noté que la extracción debía realizarse de tal manera que cada palabra (y sus aspectos) de la clave en particular tuvieran que acumularse juntas como un valor para la clave. Esto significaba que era necesario almacenar cada palabra, significado y ejemplo dentro de una tupla. Cada tupla debía almacenarse dentro de una única lista que se representaría a sí misma como el valor de una clave en particular. Esto se muestra a continuación:

Para esto, planeé recopilar cada palabra, significado y oración de cada clave dentro de una lista separada encerrada por otra lista, digamos key-list. Nuevamente, la imagen le dirá con mayor precisión:

Para hacer esto, agregué el siguiente código al que escribí para cortar:

La lógica de este código (la parte else) resultó ser incorrecta, desafortunadamente. Supuse erróneamente que solo existían 2 condiciones ('=' y ':') en el texto. Hubo muchas excepciones que no noté. Terminé perdiendo horas para depurar posibles errores en la lógica. Había asumido que el archivo de texto completo seguía el mismo patrón. Pero ese simplemente no fue el caso.

Incapaz de progresar, pasé a la siguiente parte del programa. Pensé que me vendría bien un poco de ayuda de mi hermano después de completar las otras partes. :PAGS

Continuará…

4. Creación de valores para las claves mediante la función Zip y Desembalaje de parámetros.

En este punto, no estaba completamente seguro de lo que haría incluso después de lograr la configuración de listas anterior. Había aprendido sobre la función 'Zip' y el 'Desempaquetado de parámetros' durante una de las charlas técnicas de mi hermano, que literalmente comprimía las listas que se le pasaban, así:

Entonces pensé que de alguna manera podría combinar esas dos características para lograr el resultado deseado. Después de un poco de vaivén, probando las características y trabajando en listas ficticias, lo logré. Creé un archivo separado (beta) para esta tarea, cuyo fragmento se proporciona a continuación:

El funcionamiento del código anterior se puede averiguar echando un vistazo a la salida:

La función zip () comprime las listas o valores correspondientes dentro de las listas y los encierra en una tupla. Las tuplas dentro de las listas se convierten luego en listas para descomprimirlas y comprimirlas. Finalmente, se obtiene el resultado deseado.

Me sentí muy aliviado porque el código funcionó esta vez. Estaba feliz de poder manipular los datos extraídos y moldearlos en el formato requerido. Copié el código en el archivo principal en el que estaba trabajando y modifiqué los nombres de las variables en consecuencia. Ahora todo lo que quedaba por hacer era asignar valores a las claves en el diccionario (¡y por supuesto la parte de extracción!).

5. Asignar valores a las claves en el diccionario.

Para esto, llegué a esta solución después de experimentar un poco con el código:

Esto produjo el resultado deseado de la siguiente manera:

El programa estaba casi terminado. El principal problema radicaba en la parte de extracción de datos.

... continuación de la sección 3

Después de horas y horas de depuración, me sentía cada vez más frustrado por saber por qué la maldita cosa no funcionaba. Llamé a mi hermano y me dio una pista sutil sobre las suposiciones que había hecho al definir los bucles condicionales y las cláusulas if-else. Examinamos el archivo de texto y notamos que algunas palabras tenían ejemplos en dos líneas en lugar de una.

De acuerdo con la lógica de mi código, dado que no hay un signo ':' en la segunda línea (ni un signo '=', para el caso), el contenido de la línea no se trataría como parte del ejemplo. Como resultado, esta declaración haría verdadera la última parte 'else' y ejecutaría el código escrito en ella. Teniendo en cuenta todo esto, modifiqué el código de la siguiente manera:

Aquí, hasNumbers () es una función que comprueba si una línea determinada tiene números. Lo definí de la siguiente manera:

Lo que hace es que recopila la segunda línea del ejemplo si todas las demás condiciones fallan, la combina con la primera línea y luego la agrega a la lista correspondiente como antes.

Para mi decepción, esto no funcionó y, en cambio, mostró un error de que el índice estaba fuera de rango. Me quedé estupefacto, ya que cada línea de código parecía ser lógicamente correcta en mi opinión.

Después de horas de locura, mi hermano me mostró una forma de buscar los números de línea donde ocurrió el error. Una de las principales habilidades en programación es la capacidad de depurar el programa, para verificar adecuadamente posibles errores y mantener un flujo continuo.

Curiosamente, la siguiente adición al código informó que el error ocurrió alrededor de la línea número 1750 del archivo de texto.

¡Esto significó que el programa funcionó bien hasta ese número de línea y que mi código era correcto! El problema radica en mis suposiciones erróneas y también en el archivo de texto gracias a su heterogeneidad.

Esta vez, noté que algunas claves no estaban por sus números, lo que causó problemas en el flujo lógico. Rectifiqué los errores modificando aún más el código de la siguiente manera:

Esto funcionó bien hasta la línea 4428 del archivo de texto, pero se bloqueó inmediatamente después. Revisé ese número de línea en el archivo de texto, pero eso no ayudó mucho. Entonces me di cuenta, para mi felicidad, de que debía ser la última línea. Todo el programa funcionó en la lista limpia que estaba vacía de nuevas líneas y espacios. Imprimí la última línea de la lista limpia y la comparé con la última línea del archivo de texto. ¡Se emparejaron!

Estaba muy feliz de saber esto, ya que significaba que el programa se ejecutó hasta el final. La única razón por la que falló fue que después de la última oración, nada del código tenía sentido. Mis condicionales fueron diseñados para verificar siempre la siguiente línea también, junto con la línea actual. Como no había línea después de la última línea, se bloqueó.

Así que escribí una línea adicional de código para cubrir eso:

Todo funcionó ahora. ¡Finalmente! ¡Ahora todo lo que tenía que hacer era asignar las claves a los valores correspondientes y eso es todo! Me tomé un descanso en este momento, considerando que mi proyecto finalmente había terminado. Le agregaría algunos toques finales más tarde.

Pero antes de tomarme un descanso, decidí incluir cada código dentro de varias funciones para que el código se vea ordenado. Ya tuve muchos problemas para navegar arriba y abajo de las líneas de código. Así que decidí tomarme un descanso después de hacer esto.

Sin embargo, después de hacerlo, el programa comenzó a dar errores de alcance variable. Me di cuenta de que esto se debía a que las variables declaradas dentro de las funciones no se pueden llamar directamente desde fuera de la función, ya que están en el espacio de nombres local. No dispuesto a hacer más cambios debido a ese error lamentable, decidí volver al mismo código con el que me había estado golpeando la cabeza desde el principio.

Sin embargo, para mi total incredulidad, el programa no funcionó de la misma manera que antes. De hecho, ¡no funcionó en absoluto! Simplemente no pude averiguar la razón (¡y todavía no puedo!). Estuve profundamente deprimido durante el resto del día. ¡Fue como experimentar una pesadilla incluso antes de quedarse dormido!

Afortunadamente y milagrosamente, el código funcionó al día siguiente después de que hice algunos cambios cuidadosos. Me aseguré de crear muchos archivos beta (para cada cambio realizado) a partir de entonces para evitar ese caos innecesario.

Después de unas horas más, finalmente pude completar mi programa (pero no hasta que consumí 4 días completos). Hice algunos cambios más como:

i) modificar la función 'hasNumbers' a la función 'hasNumbersDot' y excluir la expresión regular que hice anteriormente en el programa. Esto hizo coincidir las claves de manera más eficiente ya que no tenía suposiciones y, por lo tanto, no había excepciones. El código es el siguiente:

ii) reemplazar la condición de expresión regular y el código para obtener claves de la lista limpia.

iii) combinar las condiciones 'si' en la parte de 'extracción de ejemplos'

iv) materializar el código para la asignación de teclas del diccionario

Además, después de algunas pruebas y errores, pude convertir los datos obtenidos en un archivo CSV bellamente estructurado:

Puede consultar mi repositorio de github en mi perfil para ver el código completo del programa, incluidos el archivo de texto y el archivo csv.

En general, fue una gran experiencia. Aprendí mucho de este proyecto. También gané más confianza en mis habilidades. A pesar de algunos eventos desafortunados (la programación involucra tales cosas: P), finalmente pude completar la tarea dada.

¡Una última cosa! Recientemente, me encontré con un meme gracioso sobre las etapas de depuración que es tan relacionado con mi experiencia que no puedo resistirme a compartirlo. xD

Gracias por llegar hasta aquí (incluso si te saltaste la mayor parte para ver el resultado final: P).