Второй день Пришествия кода. Сегодня мы будем работать со структурой и посмотрим на оператора космического корабля.
Как всегда, пожалуйста, попробуйте решить проблему, прежде чем искать решение. Для всех статей в этой серии ознакомьтесь с этим списком.
День 2: Часть 1
Нам даются инструкции в текстовом виде. Формат основан на строке с одной командой в каждой строке, при этом каждая команда формируется из направления и числового значения.
forward 10 down 2 up 1
Синтаксический анализ текста — одна из областей, в которых стандартная библиотека C++ не особенно хороша. Средства высокого уровня не очень эффективны, а средства низкого уровня не особенно просты в использовании. Я буду придерживаться средств высокого уровня, но укажу, где код особенно неэффективен.
Нам понадобится способ отслеживать позицию при разборе ввода. Самый простой способ — создать простую структуру с двумя полями: depth
, которое настраивается с помощью команд up
и down
, и distance
, на которое влияет команда forward
.
Мы можем упростить себе задачу и работать с каждой строкой отдельно. Поскольку каждая команда преобразуется в дельту позиции, мы можем проанализировать строку и вернуть Position
. Затем внешний код может добавить дельту к рабочему положению.
Как и в День 1, мы начнем с написания тестов для этой функции синтаксического анализа строк:
Обратите внимание, что этот код теперь требует, чтобы Position
было сопоставимо из-за EXEPECT_EQ
. Мы можем быстро исправить это, добавив в нашу структуру оператор космического корабля по умолчанию. Реализация по умолчанию предоставляет лексикографический компаратор, но нам нужны только ==
и !=
.
Теперь пришло время реализовать синтаксический анализ строки:
Сначала мы находим пробел, отделяющий команду от числа (строка 2), затем преобразуем число в целое число (строка 3) и возвращаем другую дельту позиции на основе команды.
Если бы нас заботила производительность, мы бы работали с std::string_view
. К сожалению, в настоящее время стандартная библиотека не предлагает средств для преобразования std::string_view
в число (напрямую), и нам пришлось бы написать обертку вокруг std::from_chars
.
С учетом сказанного, пока мы принудительно копируем std::string
, все команды, которые мы анализируем, должны удобно вписываться в оптимизацию небольшой строки. Таким образом, даже с копиями мы не должны выделять память, а стоимость должна быть эквивалентна использованию std::string_view
.
Чтобы проанализировать весь ввод, мы переходим построчно и вызываем наш код разбора строки. Но сначала тестовый код:
Одна вещь, которую мы все еще упускаем, — это способ сложить дельты позиций вместе. Мы можем добавить operator+=
в нашу структуру Position
, чтобы нам не приходилось вручную вносить изменения.
С помощью синтаксического анализатора строк и Position
с поддержкой operator +=
весь код синтаксического анализа значительно упрощается:
Мы идем построчно, пока не достигнем конца файла, применяем наш анализатор строк к каждой строке и добавляем результат к промежуточному итогу.
День 2: Часть 2
Вторая часть задачи представляет интересный поворот. Вместо того, чтобы up
и down
обозначали прямое движение, они меняют направление, а затем forward
применяют это направление.
До сих пор наш код не имел состояния, и каждая строка интерпретировалась изолированно. Введение направления изменяет значение forward
. Следовательно, мы должны отслеживать направление, изменив наш код синтаксического анализа строки, чтобы он сохранял состояние. Но давайте начнем с настройки нашей структуры Position
.
Перемещение необработанных операций в функции-члены сделает наш код синтаксического анализа более понятным для чтения.
Этот тест также определяет интерфейс того, как мы предоставляем состояние синтаксическому анализатору строк. У нас есть другие варианты, и, например, этот код синтаксического анализа также может быть функцией-членом нашей структуры position: pos.parse(line)
.
При всей этой подготовке наш парсер строк очень похож на вариант из части 1:
И собираем все это вместе тоже очень похожим образом:
Мы по-прежнему вызываем анализатор строк для каждой строки, как и раньше. Однако на этот раз мы переходим в состояние, которое сохраняется при всех вызовах.
Наконец, поскольку я не показывал ее в первой части, давайте посмотрим на нашу основную функцию, которая очень похожа на Day1.
Ссылки и технические примечания
Репозиторий ежедневных решений доступен по адресу: https://github.com/HappyCerberus/moderncpp-aoc-2021.
Для получения статей о других днях Advent of Code ознакомьтесь с этим списком.
И, пожалуйста, не забудьте попробовать Пришествие кода на себе.
Спасибо за чтение
Спасибо, что прочитали эту статью. Вам понравилось?
Я также публикую видео на YouTube. У тебя есть вопросы? Напишите мне в Twitter или LinkedIn.