Основы Python для академического успеха: от структур данных до объектно-ориентированного подхода

Курс систематизирует базовые знания и готовит студента к вузовской программе по Computer Science. Программа охватывает продвинутый синтаксис, принципы ООП и алгоритмическую логику для решения сложных учебных задач.

1. Углубленная работа со структурами данных: списки, словари и множества

Углубленная работа со структурами данных: списки, словари и множества

Представьте, что вы пишете систему для управления университетской библиотекой. Вам нужно не просто хранить названия книг, но и мгновенно находить их по уникальному коду, отслеживать уникальных посетителей и поддерживать историю выдачи в строгом порядке. Обычных переменных здесь недостаточно — данные требуют структуры, которая определяет скорость и логику работы всей программы. В академической среде Python ценится именно за то, как изящно он управляет коллекциями объектов, позволяя исследователю сосредоточиться на алгоритме, а не на распределении памяти.

Списки как динамические массивы и их скрытые механизмы

Списки в Python — это не просто перечни элементов. С технической точки зрения, это динамические массивы, которые хранят ссылки на объекты. Когда вы создаете список students = ["Ivan", "Maria"], Python выделяет в памяти блок под указатели. Если список растет, язык автоматически перевыделяет память с запасом, что делает операцию добавления элемента в конец (.append()) очень быстрой. Однако удаление или вставка в середину списка заставляет систему сдвигать все последующие элементы, что при работе с миллионами записей (например, результатами физического эксперимента) может парализовать вычисления.

Особое внимание стоит уделить списковым включениям (list comprehensions). Это не просто «синтаксический сахар», а эффективный способ создания новых списков на основе существующих. Вместо того чтобы инициализировать пустой список и заполнять его в цикле for, вы описываете трансформацию в одну строку. Это работает быстрее, так как итерация происходит на уровне оптимизированного C-кода интерпретатора.

> Инсайт: Эффективность кода часто зависит от выбора правильного метода изменения структуры. Используйте .append() для накопления данных и избегайте вставки в начало списка .insert(0, x), если размер данных превышает несколько тысяч элементов.

Срезы и продвинутая навигация

Срезы (slices) позволяют извлекать подмножества данных с хирургической точностью. Синтаксис list[start:stop:step] кажется простым, но он скрывает мощный инструмент анализа. Например, в биоинформатике с помощью срезов можно инвертировать последовательность нуклеотидов или выделять каждый третий кодон. Важно помнить, что срез всегда создает копию части списка. Если вы работаете с гигантским массивом данных, неосторожное использование срезов в цикле может быстро исчерпать оперативную память вашего рабочего сервера.

Словари: магия хэш-таблиц и мгновенный поиск

Если списки — это полки с книгами, где нужно знать номер полки, то словари (dict) — это каталог, где вы находите книгу по её уникальному шифру. Словари реализуют структуру данных, называемую хэш-таблицей. Основное преимущество здесь — константное время поиска. Независимо от того, 10 записей в вашем словаре или 10 миллионов, Python найдет значение по ключу практически мгновенно. Это критически важно для задач индексации, например, при подсчете частоты слов в огромном корпусе текстов для лингвистического исследования.

Ключом в словаре может быть только неизменяемый (hashable) объект: строка, число или кортеж. Это связано с тем, что Python вычисляет хэш-код ключа один раз, чтобы определить его «адрес» в памяти. Если бы ключ изменился, адрес стал бы неверным, и данные были бы потеряны. Значения же могут быть любыми, включая вложенные словари или списки, что позволяет создавать сложные древовидные структуры данных.

| Характеристика | Список (List) | Словарь (Dict) | | :--- | :--- | :--- | | Доступ к данным | По индексу (целое число) | По ключу (любой hashable объект) | | Порядок | Сохраняет порядок добавления | Сохраняет порядок (с Python 3.7+) | | Скорость поиска | Медленно (нужно перебрать всё) | Очень быстро (хэш-таблица) | | Основное применение | Упорядоченные коллекции | Ассоциативные массивы, базы данных |

Множества и теоретико-множественные операции

Множества (set) — это неупорядоченные коллекции уникальных элементов. В академических задачах они незаменимы, когда нужно избавиться от дубликатов или выполнить операции из классической математики: объединение, пересечение и разность. Представьте, что у вас есть два списка студентов, записавшихся на разные курсы. Чтобы найти тех, кто посещает оба предмета, достаточно одной операции пересечения set_a & set_b.

Множества, как и словари, используют хэширование. Это означает, что проверка наличия элемента в множестве if x in my_set происходит мгновенно. В то время как поиск в списке требует последовательного сравнения x с каждым элементом, что делает списки крайне неэффективными для задач фильтрации уникальных значений.

Пошаговый разбор: Обработка данных научного опроса

Предположим, мы получили данные о результатах тестирования студентов в виде списка кортежей, где каждый кортеж содержит ID студента, название предмета и балл: raw_data = [(101, "Math", 90), (102, "Bio", 85), (101, "Math", 95)]. Наша задача — оставить только лучшие результаты для каждого студента по каждому предмету и составить список уникальных предметов.

Шаг 1: Инициализация структуры для хранения. Мы будем использовать словарь, где ключом будет пара (ID, предмет), а значением — максимальный балл. Это гарантирует, что мы легко соотнесем данные. best_scores = {}

Шаг 2: Итерация и фильтрация. Проходим по raw_data. Для каждой записи проверяем, есть ли уже такой ключ в словаре. Если нет или новый балл выше старого — обновляем значение.

Шаг 3: Сбор уникальных названий предметов. Чтобы получить список всех предметов без повторов, идеально подходит множество. subjects = {subject for student_id, subject, score in raw_data}

Шаг 4: Преобразование для отчета. Теперь мы можем превратить наш словарь обратно в список или вывести его в удобном виде. Использование словаря позволило нам избежать вложенных циклов и сделать код читаемым и быстрым.

Распространенные ошибки и нюансы производительности

Частая ошибка начинающих — использование списков там, где требуются множества. Например, проверка if user in banned_users_list внутри цикла превращает программу в «улитку», если список забаненных пользователей велик. Перевод этого списка в set ускоряет выполнение в тысячи раз.

Еще один важный нюанс — копирование объектов. Когда вы пишете list_b = list_a, вы не создаете новый список, а лишь создаете вторую ссылку на тот же объект в памяти. Изменение list_b отразится на list_a. Для создания независимой копии используйте метод .copy() или модуль copy для глубокого копирования вложенных структур. В научных расчетах, где данные часто трансформируются на разных этапах, непонимание разницы между поверхностной и глубокой копией — прямой путь к неверным результатам эксперимента.

Если из этой главы запомнить три вещи — это: используйте списки для хранения порядка, словари для быстрого доступа по ключу и множества для работы с уникальностью. Понимание того, как эти структуры работают «под капотом», превращает вас из человека, пишущего код, в инженера, проектирующего эффективные системы.

2. Функциональное программирование, область видимости и модульность кода

Функциональное программирование, область видимости и модульность кода

В программировании существует концепция «чистоты», которая напрямую влияет на то, насколько легко будет поддерживать ваш код спустя полгода. Когда вы переходите от простых скриптов к созданию серьезного академического ПО, функции перестают быть просто «кусками кода, которые можно вызвать». Они становятся кирпичиками логики, которые должны быть предсказуемыми, независимыми и легко заменяемыми. Понимание того, как данные передаются внутрь функции и как они взаимодействуют с внешним миром, — это грань между хаотичным набором строк и профессиональной архитектурой.

Функции как объекты первого класса

В Python функции являются объектами первого класса. Это означает, что функцию можно присвоить переменной, передать в качестве аргумента другой функции или даже вернуть из функции. Это открывает двери к функциональному стилю программирования. Например, если у вас есть список функций для обработки текста (очистка от знаков препинания, перевод в нижний регистр, стемминг), вы можете просто пройтись циклом по этому списку, применяя каждую функцию к входным данным.

Особое место занимают лямбда-функции — анонимные функции, состоящие из одного выражения. Они незаменимы для коротких операций, например, при сортировке сложных структур данных. Если у вас есть список словарей с данными студентов, вы можете отсортировать их по среднему баллу всего одной строкой: sorted(students, key=lambda x: x['gpa']). Здесь лямбда-функция служит временным инструментом, который не засоряет общее пространство имен.

Области видимости: правило LEGB

Одной из самых частых причин ошибок у студентов является непонимание того, где «живет» переменная. Python использует правило LEGB для поиска имен переменных:

  • Local (Локальная) — внутри функции.
  • Enclosing (Замыкающая) — во внешней функции (для вложенных функций).
  • Global (Глобальная) — на уровне модуля (файла).
  • Built-in (Встроенная) — зарезервированные имена Python (например, len или print).
  • Представьте, что вы определили переменную x = 10 в начале файла, а затем внутри функции написали x = 5. Python создаст новую локальную переменную x, которая никак не повлияет на глобальную. Это защитный механизм: функции не должны случайно менять состояние всей программы. Если вам действительно нужно изменить глобальную переменную (что в академическом коде считается плохим тоном), используется ключевое слово global. Однако гораздо правильнее передать значение в функцию и вернуть результат через return.

    > Инсайт: Избегайте использования глобальных переменных для передачи данных между функциями. Это делает код «хрупким»: изменение в одном месте может незаметно сломать логику в другом, что недопустимо в научных расчетах.

    Модульность и организация проекта

    Когда ваш код перерастает 200-300 строк, его становится трудно читать. Решение — модульность. Модуль в Python — это просто файл с расширением .py. Вы можете вынести все математические формулы в math_utils.py, а логику обработки данных — в data_processor.py. Это не только упрощает чтение, но и позволяет повторно использовать код в других проектах.

    Важным аспектом является использование конструкции if __name__ == "__main__":. Она позволяет разделять код, который должен выполниться при запуске файла, от кода, который просто определяет функции для импорта. Без этой проверки, если вы импортируете модуль в другой файл, весь его «тестовый» код выполнится автоматически, что часто приводит к неожиданным побочным эффектам.

    Пошаговый разбор: Создание системы обработки экспериментальных данных

    Допустим, нам нужно создать инструмент для обработки температурных датчиков. У нас есть «грязные» данные, и нам нужно их отфильтровать, нормализовать и сохранить.

    Шаг 1: Проектирование чистых функций. Создадим функцию clean_data(raw_values), которая убирает значения за пределами разумного диапазона (например, ошибки датчика). Она должна принимать список и возвращать новый список, не меняя оригинал.

    Шаг 2: Реализация логики трансформации. Создадим функцию normalize(values, factor). Здесь мы можем использовать map() или списковое включение для применения коэффициента к каждому элементу.

    Шаг 3: Объединение в конвейер. Напишем главную функцию process_experiment(data), которая последовательно вызывает clean_data и normalize. Это делает структуру прозрачной: вы сразу видите этапы обработки.

    Шаг 4: Вынос в модуль. Поместим эти функции в файл tools.py. Теперь в основном скрипте мы можем написать from tools import process_experiment, скрыв детали реализации и оставив только высокоуровневую логику.

    Замыкания и декораторы: взгляд за горизонт

    Иногда функции нужно «запомнить» состояние среды, в которой она была создана. Это называется замыканием. На основе замыканий строятся декораторы — мощный инструмент Python, позволяющий изменять поведение функций, не меняя их исходный код. В научном программировании декораторы часто используют для логирования времени выполнения алгоритма или для автоматического кэширования результатов сложных вычислений.

    Например, декоратор @timer может автоматически замерять, сколько секунд заняла обработка массива данных. Вы просто «навешиваете» его над определением функции, и он берет на себя всю рутину по работе с системным временем. Это позволяет держать прикладной код чистым от служебных инструкций.

    Если из этой главы запомнить три вещи — это: делайте функции маленькими и решающими одну задачу, всегда помните о правиле LEGB, чтобы не «терять» переменные, и разделяйте код на логические модули. Программа, состоящая из независимых модулей, подобна конструктору: её легко чинить, расширять и тестировать, что является золотым стандартом в современной разработке.

    3. Надежность программ: обработка ошибок и работа с исключениями

    Надежность программ: обработка ошибок и работа с исключениями

    В идеальном мире программы всегда получают корректные данные, интернет-соединение никогда не обрывается, а пользователи вводят только то, что от них просят. В реальности академический код часто сталкивается с «грязными» данными: пропущенные значения в CSV-файлах, деление на ноль при статистических расчетах или отсутствие прав доступа к папке с результатами. Профессиональный программист отличается от любителя тем, что он предвидит эти ситуации и заставляет программу «падать грациозно», а не с пугающим дампом ошибок в консоли.

    Природа исключений: почему программы «ломаются»

    Исключение в Python — это объект, который создается в момент возникновения ошибки. Когда интерпретатор встречает ситуацию, с которой не может справиться (например, попытка открыть несуществующий файл FileNotFoundError), он «выбрасывает» (raise) исключение. Если это исключение не «поймать», выполнение программы немедленно прекращается.

    Важно понимать разницу между синтаксическими ошибками (которые проверяются до запуска кода) и исключениями (которые возникают во время выполнения). Исключения — это не всегда плохо. Часто это способ управления логикой. Например, итерация в Python по своей сути заканчивается исключением StopIteration, которое цикл for просто умеет обрабатывать незаметно для вас.

    Конструкция try-except: ваш страховочный трос

    Основной инструмент работы с ошибками — блок try-except. Логика проста: «попробуй выполнить этот код, и если возникнет конкретная проблема, сделай вот это».

    Критически важно перехватывать конкретные исключения. Использование «голого» except: без указания типа ошибки — опасная практика. Это может скрыть серьезные системные сбои или даже лишить вас возможности прервать программу нажатием Ctrl+C (так как это тоже генерирует исключение KeyboardInterrupt). Всегда стремитесь быть максимально точными: если вы ждете ошибку ввода-вывода, ловите IOError, если ошибку ключа в словаре — KeyError.

    Полный цикл: else и finally

    Блок try может быть дополнен секциями else и finally.
  • else выполняется только в том случае, если в блоке try не возникло ошибок. Это идеальное место для логики, которая должна сработать только при успешном завершении операции.
  • finally выполняется всегда, независимо от того, была ошибка или нет. Обычно здесь закрывают файлы, разрывают соединения с базами данных или освобождают внешние ресурсы.
  • Пошаговый разбор: Безопасное чтение научной конфигурации

    Представьте, что ваша программа должна прочитать параметры эксперимента из текстового файла. Файл может отсутствовать, быть пустым или содержать текст вместо чисел.

    Шаг 1: Попытка открытия файла. Мы помещаем open() в блок try. Если файла нет, Python выбросит FileNotFoundError.

    Шаг 2: Чтение и преобразование данных. Внутри того же блока мы пытаемся преобразовать строку в число: value = float(line). Здесь может возникнуть ValueError, если в файле написано "два" вместо "2".

    Шаг 3: Обработка разных сценариев. Мы добавляем несколько блоков except. Один для FileNotFoundError (сообщаем пользователю, что нужно проверить путь), другой для ValueError (сообщаем о некорректном формате данных).

    Шаг 4: Гарантированная очистка. Даже если чтение прошло наполовину успешно и возникла ошибка, нам нужно убедиться, что файл закрыт. Хотя менеджер контекста with делает это автоматически (и это лучший способ), понимание finally важно для работы с более сложными ресурсами, такими как сетевые сокеты.

    Создание собственных исключений и LBYL vs EAFP

    В сложных проектах стандартных исключений Python может не хватать. Вы можете создавать свои классы ошибок, наследуясь от базового класса Exception. Например, InsufficientDataError для случая, когда выборка для анализа слишком мала. Это делает ваш код самодокументированным: другой программист сразу поймет, в чем именно заключается бизнес-логика ошибки.

    В сообществе Python преобладает философия EAFP (Easier to Ask for Forgiveness than Permission — «проще попросить прощения, чем разрешения»). Вместо того чтобы сначала проверять, существует ли файл (if os.path.exists...), вы просто пытаетесь его открыть и обрабатываете ошибку, если она возникнет. Это работает быстрее и избавляет от «состояния гонки» (когда файл мог быть удален в микросекунду между проверкой и открытием).

    > Инсайт: Обработка исключений — это не способ скрыть баги в коде. Это способ обработки внешних факторов, которые вы не можете контролировать на 100%.

    Если из этой главы запомнить три вещи — это: никогда не используйте пустой except:, используйте finally для освобождения ресурсов и следуйте принципу EAFP для написания более чистого и быстрого кода. Умение предвидеть ошибки делает ваши исследования воспроизводимыми, а программы — надежными инструментами, на которые можно положиться.

    4. Основы объектно-ориентированного программирования: классы, объекты и инкапсуляция

    Основы объектно-ориентированного программирования: классы, объекты и инкапсуляция

    Когда мы решаем простые задачи, нам достаточно функций. Но представьте, что вы моделируете целую экосистему университета. У вас есть тысячи студентов, профессоров, аудиторий и курсов. У каждого студента есть имя, список оценок, номер зачетки, и каждый может выполнять действия: записываться на курс, сдавать экзамен, получать стипендию. Если описывать это только через словари и функции, код превратится в запутанный клубок. Объектно-ориентированное программирование (ООП) позволяет объединить данные и действия в одну логическую единицу — объект.

    Класс как чертеж, объект как здание

    Класс — это шаблон или «чертеж». Он описывает, какими свойствами будет обладать объект и что он сможет делать. Объект (или экземпляр класса) — это конкретное воплощение этого чертежа. Например, класс Student описывает абстрактного студента, а объект ivan = Student("Иван") — это живой человек с конкретным именем.

    Внутри класса мы определяем:

  • Атрибуты — переменные, которые хранят состояние объекта (например, self.name).
  • Методы — функции, которые описывают поведение объекта (например, self.enroll_course()).
  • Ключевое слово self часто пугает новичков. На самом деле это просто ссылка на текущий экземпляр объекта. Когда вы вызываете метод ivan.study(), Python неявно передает объект ivan в качестве первого аргумента в метод, чтобы тот знал, чьи именно данные нужно изменить.

    Инкапсуляция: защита внутренних механизмов

    Инкапсуляция — это принцип «черного ящика». Мы скрываем внутреннюю сложность объекта и предоставляем только простой интерфейс для взаимодействия. В Python нет жестких ограничений доступа (как private в Java), но есть соглашения.

  • Имя с одним подчеркиванием (_variable) — это сигнал другим программистам: «Это внутренняя деталь, не трогайте её напрямую».
  • Имя с двумя подчеркиваниями (__variable) включает механизм искажения имен, делая атрибут труднодоступным извне.
  • Зачем это нужно? Представьте, что у объекта BankAccount есть атрибут balance. Если разрешить менять его напрямую (account.balance = 1000000), это приведет к катастрофе. Правильный путь — создать метод deposit(), который проверит легитимность операции перед изменением баланса. Это и есть инкапсуляция — защита целостности данных.

    | Понятие | Аналогия из жизни | Роль в коде | | :--- | :--- | :--- | | Класс | Рецепт торта | Описание структуры и логики | | Объект | Конкретный торт на столе | Данные в памяти | | Инкапсуляция | Корпус тостера | Скрытие электрики за кнопками |

    Пошаговый разбор: Создание системы учета публикаций

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

    Шаг 1: Определение класса. Создаем класс Publication. В методе __init__ (конструкторе) инициализируем заголовок, авторов и количество цитирований. def __init__(self, title, authors): self.title = title; self.citations = 0

    Шаг 2: Добавление поведения. Добавляем метод add_citation(), который увеличивает счетчик. Это гарантирует, что цитирование не может стать отрицательным (мы добавим проверку внутри метода).

    Шаг 3: Создание объектов. Создаем несколько публикаций: pub1 = Publication("AI in Medicine", ["Smith", "Doe"]). Теперь каждый объект живет своей жизнью, храня свои данные.

    Шаг 4: Взаимодействие. Мы можем создать список объектов и в цикле вызвать у каждого метод get_summary(). Это демонстрирует мощь ООП: мы работаем с высокоуровневыми сущностями, а не с индексами массивов.

    Почему ООП важно для академического успеха?

    В современной науке программы редко пишутся в одиночку. Использование ООП позволяет разделять ответственность. Один исследователь может написать класс для работы с телескопом, другой — класс для анализа спектра звезд. Благодаря четким интерфейсам, эти части легко соединяются.

    Более того, большинство библиотек, которые вы будете использовать (например, pandas для анализа данных или matplotlib для графиков), построены на принципах ООП. Понимая, что такое методы и атрибуты, вы перестанете воспринимать код библиотек как магические заклинания и начнете видеть в них логичную структуру объектов.

    > Инсайт: ООП — это не про усложнение кода, а про управление сложностью. Если вы можете описать задачу в терминах «кто что делает», значит, вы готовы к проектированию классов.

    Если из этой главы запомнить три вещи — это: класс — это шаблон, а объект — его реализация; self всегда указывает на конкретный экземпляр; инкапсуляция помогает избежать случайной порчи данных. Переход к объектному мышлению — это качественный скачок, который превращает вас из кодера в архитектора программных систем.

    5. Алгоритмическое мышление и применение изученных концепций в решении задач

    Алгоритмическое мышление и применение изученных концепций в решении задач

    Программирование — это не знание синтаксиса, а умение разбивать сложную, пугающую задачу на последовательность простых и понятных шагов. В академической среде вы часто будете сталкиваться с проблемами, для которых нет готовой кнопки или функции. Вам придется конструировать решение из тех инструментов, которые мы изучили: структур данных, функций, обработки ошибок и объектов. Алгоритмическое мышление — это мост между постановкой научной задачи и её программной реализацией.

    Декомпозиция: разделяй и властвуй

    Первый шаг любого алгоритма — декомпозиция. Если вам нужно проанализировать геном человека, не пытайтесь написать одну функцию analyze_genome(). Разбейте её на части:

  • Считать данные из файла (с обработкой исключений).
  • Очистить последовательность от шумов (используя методы строк или списков).
  • Найти повторяющиеся паттерны (используя словари для подсчета частоты).
  • Вывести отчет (используя классы для форматирования данных).
  • Каждая из этих подзадач решается гораздо проще. Более того, если на этапе чтения файла возникнет ошибка, вы будете точно знать, в каком блоке кода её искать. Это делает ваш код тестируемым и надежным.

    Выбор структуры данных как ключ к эффективности

    Часто выбор правильной структуры данных — это уже 90% решения. Ошибка в выборе может превратить задачу, решаемую за секунды, в процесс, требующий часов вычислений.

  • Нужно быстро проверять наличие элемента? Используйте set.
  • Нужно хранить пары «термин — определение»? Используйте dict.
  • Нужно сохранять строгую последовательность замеров? Используйте list.
  • Вспомните наш пример с библиотекой. Если вы храните список книг в обычном списке и ищете книгу по названию, при каждом поиске Python будет просматривать все книги с первой до последней. Если книг миллион, это займет время. Если же вы используете словарь, где ключ — название книги, поиск произойдет мгновенно. Это и есть алгоритмическая оптимизация на уровне структур данных.

    Пошаговый разбор: Алгоритм поиска кратчайшего пути (упрощенный)

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

    Шаг 1: Представление данных. Лучший способ представить граф (сеть) — это словарь, где ключи — это названия корпусов, а значения — списки соседних корпусов, с которыми есть связь. graph = {'Main': ['Library', 'Lab'], 'Library': ['Main', 'Dorm']}

    Шаг 2: Выбор стратегии. Мы будем использовать поиск в ширину (BFS). Нам понадобится очередь (список) для хранения корпусов, которые нужно посетить, и множество (set) для хранения уже посещенных мест, чтобы не ходить по кругу.

    Шаг 3: Реализация цикла. Пока очередь не пуста, берем первый корпус, проверяем, не является ли он целью. Если нет — добавляем всех его соседей в очередь, если мы там еще не были.

    Шаг 4: Обработка результата. Если очередь опустела, а цель не найдена — возвращаем сообщение, что пути нет. Здесь мы можем использовать собственные исключения, например PathNotFoundError.

    Сложность алгоритмов: Big O на пальцах

    В университете вы обязательно столкнетесь с понятием Big O нотации. Это способ описать, как время выполнения программы растет с увеличением объема данных.

  • — константное время (поиск в словаре).
  • — линейное время (поиск в списке).
  • — квадратичное время (вложенные циклы, например, сравнение каждого элемента с каждым).
  • Стремитесь избегать вложенных циклов там, где можно применить хэширование (словари и множества). Для академических задач, связанных с Big Data или сложным моделированием, это критическая разница.

    > Инсайт: Хороший алгоритм — это не тот, который написан «умно» и непонятно, а тот, который эффективно использует ресурсы компьютера и легко читается коллегами.

    Если из этой главы запомнить три вещи — это: всегда разбивайте задачу на мелкие функции; выбирайте структуру данных, исходя из типа доступа к ним; всегда думайте о том, что произойдет с вашим кодом, если объем данных вырастет в 1000 раз. Программирование — это искусство превращать хаос в структуру, и теперь у вас есть все необходимые инструменты, чтобы начать этот путь в университете.