Мій перший проект на Python: як я перетворив неорганізований текстовий файл на акуратний CSV-файл

Тому я вирішив вивчити Python. Виявляється, ця мова комп'ютерного програмування не така вже й складна (ну, поки я не отримав цей проект!: P).

За лічені секунди я закохався в його легкий, чіткий синтаксис та автоматичне відступ під час письма. Мене зачарувало, коли я дізнався, що такі структури даних, як списки, кортежі та словник, можна створювати та ініціалізувати динамічно одним рядком (наприклад, list-name = []).

Більше того, значення, що містяться в них, могли бути доступні з використанням індексів та без них. Це робить код дуже читабельним, оскільки індекс замінюється англійським словом на вибір.

Ну, досить сказано про мову. Дозвольте мені показати вам, чого вимагав проект.

Мій брат дав мені цей проект. Він натрапив на текстовий файл, що містить тисячі слів. Багато слів мали майже однакове значення. Кожне слово мало своє визначення та приклад речення поруч, але не надто організовано. Між словом та його реченням були пробіли та нові рядки. У словах відсутні деякі аспекти. Нижче наведені фрагменти текстового файлу, про який я кажу:

Він хотів, щоб аспекти тексту були єдиними. Для цього йому потрібен був я акуратно відсортувати всі подібні слова, що мають значення поруч із темою. Він сказав мені, що цього можна досягти, захопивши всі дані тексту в словник у наступному форматі:

а потім записати їх у файл CSV (значення, розділені комами).

Він запитав, чи можу я взяти це за свій перший проект, тепер, коли я вивчив основи. Мені було приємно розробити логіку, і я негайно погодився. Коли його запитали про кінцевий термін, він дав мені пристойний час - 2 дні, щоб закінчити.

На жаль, у підсумку я зайняв подвійну кількість часу, оскільки намагався правильно налагодити написаний код. Чесно кажучи, якби не короткі візити мого брата до моєї кімнати, щоб подивитися на прогрес і натякнувши на неправильні припущення, зроблені мною під час написання умов, мені судилося закінчити проект у вічності: P

Я почав із створення міні-завдань в рамках програми, які я прагнув закінчити, перш ніж будувати всю програму. Вони були перелічені нижче:

1. Формування регулярного виразу відповідно до числа та слова поруч із ним.

Я розглянув текстовий файл і помітив, що перед кожною темою (тут іменується "ключ") є номер перед цим. Отже, я написав кілька рядків коду для створення регулярного виразу (регулярний вираз - потужний інструмент для вилучення тексту) шаблону наступним чином:

Однак, коли я запустив це, я отримав помилку UnicodeDecodeError, точніше, що означало, що я не мав доступу до текстового файлу. Я шукав це в //stackoverflow.com і після довгих пошуків, яким не пощастило, мій брат прийшов і знайшов рішення. Виправлено помилку наступним чином:

Тим не менше, я не отримав бажаного результату. Це було пов’язано з тим, що деякі клавіші мали в тексті скісні риски ('/') або пробіли (''), яким міг відповідати мій регулярний вираз. Я думав покращити вираз регулярного виразу пізніше, і тому написав коментар до нього.

2. Отримання списку рядків як рядків з текстового файлу

Для цього я написав лише 1 рядок коду, і на щастя, помилок не виявилося.

Однак я отримав нечистий список. Він містив нові рядки ('\ n') та пробіли (''). Потім я намагався уточнити список наступним чином:

3. Виділення слів, значень та прикладів речень окремо та додавання їх до відповідних списків.

Це було найважче зробити, оскільки воно передбачало належну логіку та правильне судження шляхом розпізнавання зразків.

Цікаво, що, переглядаючи текстовий файл, я помітив більше шаблонів. Кожне слово мало своє значення в одному рядку, розділеному знаком '='. Крім того, перед кожним прикладом стояли знаки ':' та ключове слово 'Example'.

Я думав скористатися регулярним виразом знову. Я знайшов альтернативне та більш елегантне рішення, нарізавши рядок (тепер це рядок у списку) відповідно до розміщення символів. Нарізка - ще одна чудова функція в python. Я написав код наступним чином:

Наведений вище код читається майже як англійська. Для кожного рядка чистого списку він перевіряє, чи має він знак '=' або ':'. Якщо так, то знайдено індекс знака і відповідно зроблено нарізку.

У першому "якщо" частина перед "=" зберігається у змінній "слово", а частина після неї - у "значення". Аналогічно для другого "якщо" ("elif - ще якщо - у цьому випадку), частина після": "зберігається у" прикладі ". І після кожної ітерації слово, значення та приклад пропозиції зберігаються у відповідних списках. Таким чином можна витягти цілі дані.

Все йде нормально. Але я зауважив, що вилучення повинно здійснюватися таким чином, щоб кожне слово (та його аспекти) конкретного ключа мали бути накопичені разом як одне значення для ключа. Це означало, що потрібно було зберігати кожне слово, значення та приклад всередині кортежу. Кожен кортеж мав зберігатися в одному списку, який представляв би себе як значення для певного ключа. Це зображено нижче:

Для цього я планував зібрати кожне слово, значення та речення кожного ключа в окремому списку, укладеному іншим списком, скажімо, списком ключів. Знову ж, картинка точно скаже вам:

Для цього я додав такий код до того, який написав для нарізки:

На жаль, логіка цього коду (інша частина) виявилася помилковою. Я помилково припустив, що в тексті існують лише 2 умови ('=' та ':'). Було багато винятків, яких я не помітив. У підсумку я витратив години на налагодження можливих помилок у логіці. Я припускав, що повний текстовий файл має той самий шаблон. Але це було просто не так.

Не вдавшись досягти успіху, я перейшов до наступної частини програми. Я подумав, що мені може знадобитися допомога брата після завершення інших частин. : P

Далі буде…

4. Створення значень для ключів за допомогою функції Zip і розпакування параметрів.

На даний момент я не був повністю впевнений, що буду робити навіть після досягнення вищевказаної конфігурації списків. Я дізнався про функцію `` Zip '' та `` Розпакування параметрів '' під час однієї з технічних переговорів мого брата, яка буквально заархівувала передані їй списки, ось так:

Тож я подумав, що зможу якось поєднати ці дві функції для досягнення бажаного результату. Трохи поворушившись, випробувавши функції та працюючи над фіктивними списками, я досяг успіху. Для цього завдання я створив окремий файл (бета-версія), фрагмент якого наведено нижче:

Робота вищезгаданого коду можна зрозуміти, подивившись на результат:

Функція zip () створює стиснення відповідних списків або значень у списках та укладає їх у кортеж. Потім кортежі всередині списків перетворюються у списки для розпакування та подальшого архівування. Нарешті, отримується бажаний результат.

Я відчув велике полегшення від коду, який працював цього разу. Я був щасливий, що міг маніпулювати витягнутими даними та формувати їх у необхідний формат. Я скопіював код в основний файл, над яким працював, і відповідно змінив імена змінних. Тепер залишалося лише призначити значення ключам у словнику (і, звичайно, частині вилучення!).

5. Присвоєння значень клавішам у словнику.

Для цього я прийшов до цього рішення після деяких експериментів з кодом:

Це дало бажаний результат наступним чином:

Програма була майже виконана. Основна проблема полягала в частині вилучення даних.

... продовження з розділу 3

Після годин та годин налагодження я все більше засмучувався, чому ця чортова штука не спрацювала. Я зателефонував своєму братові, і він дав мені тонкий підказку щодо припущень, які я зробив, визначаючи умовні цикли та речення if-else. Ми детально вивчили текстовий файл і помітили, що деякі слова мають приклади у два рядки замість одного.

Відповідно до моєї логіки коду, оскільки у другому рядку немає знака ':' (а також знаку '='), вміст рядка не буде розглядатися як частина прикладу. Як результат, це твердження зробить останню частину "else" істинним і виконає написаний у ньому код. Враховуючи все це, я змінив код, як показано нижче:

Тут hasNumbers () - це функція, яка перевіряє, чи є в даному рядку цифри. Я визначив це наступним чином:

Це робить те, що він збирає другий рядок прикладу, якщо всі інші умови не вдаються, поєднує його з першим рядком, а потім додає йому відповідний список, як і раніше.

На моє розчарування, це не спрацювало, а натомість показало помилку, що індекс вийшов за межі діапазону. Я був ошалелий, оскільки кожен рядок коду здавався логічно правильним на мій погляд.

Після годин божевілля мій брат показав мені спосіб отримати номери рядків, де сталася помилка. Однією з основних навичок програмування є здатність налагоджувати програму, правильно перевіряти наявність можливих помилок і підтримувати безперервний потік.

Цікаво, що наступне доповнення до коду повідомляло, що помилка сталася біля номера рядка 1750 текстового файлу.

Це означало, що програма працювала добре до цього номера рядка і що мій код був правильним! Проблеми полягали в моїх неправильних припущеннях, а також у текстовому файлі завдяки його неоднорідності.

Цього разу я помітив, що деякі клавіші були не за їх номерами, що спричинило проблеми в логічному потоці. Я виправив помилки, змінивши код наступним чином:

Це добре працювало до рядка 4428 текстового файлу, але відразу ж зазнало аварії. Я перевірив номер рядка в самому текстовому файлі, але це не дуже допомогло. Тоді я на своє щастя зрозумів, що це, мабуть, останній рядок. Вся програма працювала над чистим списком, в якому не було нових рядків та пробілів. Я надрукував останній рядок чистого списку і порівняв його з останнім рядком текстового файлу. Вони збіглися!

Я був надзвичайно радий дізнатися про це, оскільки це означало, що програма була виконана до кінця. Єдина причина, через яку він розбився, полягає в тому, що після останнього речення жоден з кодів не мав сенсу. Мої умови були розроблені для того, щоб кожного разу перевіряти наступний рядок разом із поточним рядком. Оскільки рядка після останнього рядка не було, він розбився.

Тому я написав додатковий рядок коду, щоб прикрити це:

Зараз все працювало. Нарешті! Тепер все, що мені потрібно було призначити ключі відповідним значенням, і все! В цей момент я зробив перерву, вважаючи, що мій проект нарешті закінчився. Пізніше я додав би до нього останні штрихи.

Але перед тим, як зробити перерву, я вирішив укласти кожен код всередину різних функцій, щоб зробити код виглядати акуратно. У мене вже було багато проблем з навігацією вгору і вниз по рядках коду. Тому я вирішив зробити перерву після цього.

Однак після цього програма почала видавати помилки змінної області. Я зрозумів, що це тому, що змінні, оголошені всередині функцій, не можна викликати безпосередньо ззовні функції, оскільки вони знаходяться в локальному просторі імен. Не бажаючи вносити подальші зміни через ту кульгаву помилку, я вирішив повернутися до того самого коду, яким я бився головою з самого початку.

Однак, на мою повну недовіру, програма працювала не так, як раніше. Насправді це взагалі не працювало! Я просто не міг зрозуміти причину (і досі не можу!). Я був в депресії до кінця дня. Це було як пережити кошмар ще до того, як заснути!

На щастя і дивом, код запрацював наступного дня після того, як я вніс ретельні зміни. Я переконався, що зробив багато бета-файлів (за кожну внесену зміну), щоб уникнути такого непотрібного хаосу.

Ще через кілька годин я зміг остаточно завершити свою програму (але лише тоді, коли спожив 4 повних дні). Я зробив ще кілька змін, таких як:

i) змінення функції 'hasNumbers' на функцію 'hasNumbersDot' та виключення регулярного виразу, який я зробив раніше в програмі. Це відповідало клавішам більш ефективно, оскільки в ньому не було припущень, а отже, і винятків. Код для нього такий:

ii) заміна умови регулярного виразу та коду для отримання ключів із чистого списку.

iii) поєднання умов "якщо" у частині "вилучення прикладів"

iv) матеріалізація коду для присвоєння словникового ключа

Крім того, після деяких спроб і помилок мені вдалося перетворити отримані дані в красиво структурований файл CSV:

Ви можете перевірити моє сховище github у моєму профілі для перегляду повного коду програми, включаючи текстовий файл та файл CSV.

Загалом, це був чудовий досвід. Я повинен навчитися так багато з цього проекту. Я також отримав більше впевненості у своїх навичках. Незважаючи на деякі невдалі події (програмування включає такі речі: P), я нарешті зміг виконати дане завдання.

Останнє! Нещодавно я натрапив на веселий мем щодо етапів налагодження, який настільки пов’язаний з моїм досвідом, що я не можу не встояти. xD

Дякуємо за те, що ви пройшли весь шлях до цього часу (навіть якщо ви пропустили більшу частину, щоб перевірити остаточний результат: P)