В этом году несколько членов моей команды присоединились ко мне, чтобы насладиться Advent of Code. Я ожидал немного повеселиться, написав небольшой код, но в итоге был удивлен тем, как много я узнал в процессе и куда меня приведет этот опыт, когда мы вступим в 2023 год.

В любом случае, что такое появление кода?

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

Головоломки можно решать на любом языке программирования, и обычно при решении головоломок существует дух сотрудничества. Каждый программист получает индивидуальные входные данные, и головоломка обычно сводится к одному значению, которое можно ввести в качестве ответа. Это позволяет участникам проверять свою работу, не требуя серверной инфраструктуры, необходимой для оценки ответа и запуска кода участников.

Головоломки обычно состоят из двух частей, и доступ ко второй части открывается после завершения первой части. Пропущенные или незавершенные дни не запрещают участие в следующих днях.

Есть конкурсная таблица лидеров, но в ней 100 записей в день, а остальные не публикуются. Большинство участников присоединяются ради удовольствия работать над сложными задачами, а не соревноваться.

Гайки и болты: как я это сделал

В первый день Advent of Code в этом году я начал проект в Xcode, содержащий 25 отдельных программ командной строки, а затем просто работал над одной в день.

Обычно я читаю задачу в момент ее выпуска (полночь по восточному поясному времени, 23:00 по моему часовому поясу). Затем я либо тратил час на настройку структур данных и анализ ввода, либо начинал думать о том, как я мог бы сделать то же самое. Затем я просыпался на следующее утро, готовый заняться проектом, прежде чем отправиться в офис на весь день.

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

Когда Advent of Code уже начался, участники (на мой взгляд) не должны экспериментировать с инфраструктурой, которую они выбрали для решения задач (некоторые небольшие отличия от этого указаны ниже). Предстоит более чем работать над созданием решений, не прибегая к ненужному техническому долгу. При этом у меня есть некоторые планы на участие в следующем году, которые, вероятно, изменят мой подход.

Снова влюбиться в Свифт

В течение 2022 года я большую часть времени размышлял на двух языках программирования: Dart и C. Добавьте немного Rust, но в основном я жил в Dart и C.

Честно говоря, я и забыл, как мне нравится язык Swift.

Хотя вещи, которые мне нравятся в языке Swift, вряд ли уникальны, некоторые из функций, которые я повторно использовал во время появления кода, были:

  • Строгая типизация данных: я иногда использовал простые кортежи для временной работы с данными, но в целом я использовал строго типизированные структуры данных.
  • Простота создания многофайловых проектов. В то время как создание make-файлов не представляет особой сложности в C, а большинство современных языков легко обрабатывают многофайловые проекты, мне понравилось, как мало я думал об этих вещах в Swift и Xcode.
  • Типы значений и ссылок ортогональны друг другу: несколько раз я пользовался преимуществами использования того или иного типа. Хотя моя общая философия написания кода Swift заключается в том, чтобы «использовать типы значений до последнего возможного момента, когда вы больше не сможете их использовать», у меня было несколько случаев принять это во внимание в меньших масштабах, чем я обычно работаю.

Новый Swift RegexBuilder

У меня также была возможность познакомиться с парой новых функций в Swift. Одной из функций, выпущенных ранее в этом году, была RegexBuilder. Это языковой компонент, который позволяет нам перейти от кратких регулярных выражений к выражениям, которые легче обосновать. В программировании есть апокрифическая поговорка: «Мне нужно было решить проблему. Я решил использовать регулярные выражения, и теперь мне нужно решить две проблемы». Я в основном избегал регулярных выражений, когда это было возможно, потому что сложность, связанная с их правильным построением, выходит за рамки того, что я обычно хочу привнести в свою жизнь.

Однако новый RegexBuilder действительно делает создание и работу с регулярными выражениями приятным, если не беззаботным занятием. Новый синтаксис проверяется во время редактирования, а это означает, что вам не нужно запускать проект несколько раз через отладчик, если вы удовлетворены результатом. Большинство проблем выявляются еще до того, как ваш код будет создан.

Например, один из форматов входных файлов в этом году имел следующую структуру:

ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32

(но значительно дольше)

Эти данные, безусловно, можно импортировать разными способами, но я приветствовал возможность использования для этого нового RegexBuilder. Написание традиционного регулярного выражения для этого может оказаться сложной задачей, но код для его интерпретации с помощью RegexBuilder довольно прост:

let operationNodeRegex = Regex {
    Capture {
        Capture {
            OneOrMore(.word)
        }
        Capture {
            ChoiceOf {
                " + "
                " - "
                " * "
                " / "
            }
        }
        Capture {
            OneOrMore(.word)
        }
    }
}

let rdpNodeRegex = Regex {
    Capture {
        Capture {
            OneOrMore(.word)
        }
        ": "
        Capture {
            ChoiceOf {
                Capture {
                    operationNodeRegex
                }
                Capture {
                    OneOrMore(.digit)
                } transform: {
                    Int($0)!
                }
            }
        }
    }
}

Вы могли бы даже заметить, что это имеет то преимущество, что оно составное, когда элемент может существовать отдельно от общей структуры. Боб Мартин обсуждает этот подход в этой статье, хотя реализация Swift на самом деле представляет собой полноценный предметно-ориентированный язык (DSL), а не обертку над традиционным кодом регулярных выражений. Подобное использование DSL обеспечивает не только выразительность, но и дополнительно гарантирует, что процесс может обрабатываться во время компиляции, а не во время выполнения.

Пакет Алгоритмы

Еще одним инструментом, который я счел ценным при решении проблемы, был пакет Swift Algorithms. Это группа алгоритмов, которые можно запускать с различными коллекциями Swift. Они имеют подходящие общие реализации и, как результат, применимы к широкому кругу задач.

Одним из алгоритмов, которые я использовал чаще всего, был алгоритм Chunked, который разбивает коллекцию на набор подколлекций с ленивой оценкой заданного размера. Хотя довольно легко разделить коллекции на пары, используя общий протокол Swift Collection, фрагменты произвольного размера или по разделителю фрагментов менее просты. Просто используя алгоритм Swift Chunked, я смог упорядочить свои данные быстро и с относительно небольшими усилиями.

Еще одним членом пакета Swift Algorithms, который я использовал, была Windows, которая предоставляет фрагменты коллекции, которые представляют собой перекрывающиеся группы. Это не особенно сложная проблема для решения, но отсутствие необходимости решать ее вообще сделало мою жизнь проще.

Я всегда советую своим младшим программистам заглянуть в эту коллекцию алгоритмов, когда у них есть нечетное преобразование, которое им нужно выполнить для элементов коллекции, и это не подвело меня для Advent of Code.

Подход к программной инженерии

Мои подходы к решению головоломок в Advent of Code больше всего основаны на подходе разработки программного обеспечения. Это означало, что каждый раз, когда я решал проблему, я писал значительно больше кода с более длинными именами переменных, чем кто-то, кто стремился к чисто быстрому решению.

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

В любом проекте важно начать с определения структур данных, а затем с обработки потоков, чтобы получить решение, которое можно понять и использовать в долгосрочной перспективе. Я верю в концепцию, что подход к любой проблеме такой же, как и к самым важным проблемам; то, что я использовал хорошие методы программирования даже в этих тривиальных краткосрочных проектах, показало мне и моим коллегам ценность сохранения верности подходу разработки программного обеспечения.

Игровые площадки Swift против приложений командной строки

Работа с другими разработчиками iOS над решениями для Advent of Code выявила поразительное наблюдение: игровые площадки Xcode могут работать медленно.

Swift Playgrounds отлично подходит для экспериментов с кодом, но на самом деле не позволяет использовать точки останова так, как мне бы хотелось. В них также сложнее перенести существующие пакеты, чем в простое приложение командной строки.

Еще одно важное соображение заключается в том, что игровая площадка iOS работает значительно медленнее, чем игровая площадка macOS. Это выбор, который делается при первоначальном создании игровой площадки, и его нельзя изменить после создания игровой площадки.

Приложения командной строки сложнее строить (например, если нужно создать представление SwiftUI), но они всегда работают быстро, а отладчик работает правильно. Кроме того, его можно собрать и запустить из консоли, что означает, что эти приложения могут выполняться даже быстрее, чем отладочные версии. Наконец, можно использовать инструменты, чтобы найти, где программа проводит большую часть своего времени; хотя это редко является проблемой, когда вам нужна эта возможность, она вам действительно нужна.

Тихий компьютер без вентилятора

Одна из замечательных особенностей участия с моими маками M1 в этом году заключается в том, что эти устройства работают прохладно и тихо. Даже в те дни, когда я использовал грубый подход к решению проблемы, когда время работы составляло несколько минут, мой ноутбук никогда не нагревался и не начинал шуметь от вентилятора (а у моего M1 Air даже нет вентилятора). Я редко замечаю подобные вещи, но когда я делаю что-то, знаю, иначе мой компьютер завыл бы, это приносит радость.

Теория графов

Одна вещь, которую я узнал во время Advent of Code 2022, заключается в том, что я не полностью разбираюсь в теории графов в том, что касается реализации алгоритмов поиска и тому подобного. Теория графов – это подход к структуре данных как к набору узлов, соединенных друг с другом через ребра. В то время как я в целом смог настроить свои данные в этих структурах, мне не удалось решить задачи с использованием обхода ребер по графу.

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

Если вы работаете над Advent of Code и у вас возникли проблемы с головоломкой или группой головоломок, лучшее, что я могу порекомендовать, — это попытаться понять основную проблему, а затем настроить путь обучения, который восполнит пробел в знаниях.

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

Библиотеки будущего

Несмотря на то, что в этом году я использовал подход Software Engineering к Advent of Code, я не полностью освоил возможность повторного использования через несколько дней. Я изменю это по мере приближения к «Пришествию кода» в 2023 году, создав новую библиотеку структур данных и методов, которые помогут мне более эффективно решать проблемы, но, что еще более важно, позволят мне реализовывать решения с небольшим дублированием работы.

Создание местного сообщества вокруг AoC

Я упомянул, что работал над этим проектом с другими разработчиками в моей организации; мы создали слабый канал для обсуждения проблем. Мы публиковали решения после обеденного перерыва (чтобы поощрить друг друга к самостоятельной работе), но нам нравилось обсуждать общий вид наших решений перед их публикацией.

Что меня поразило, так это то, что даже когда два разработчика работали на одном языке, решения в конечном итоге оказывались совершенно разными. Свидетельством качества головоломок является то, что не существует универсального решения, а также то, что они выдержали тщательную проверку.

Навыки

Если вас это заинтриговало и вы хотели бы принять участие в следующем году (или даже прямо сейчас), есть несколько основных навыков, которыми вы должны владеть.

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

  • как пользоваться отладчиком: использование операторов `print` для оценки потока и выполнения программы не заменяет возможность проверять программу во время ее выполнения, чтобы понять, что происходит.
  • как профилировать вашу программу: вы должны иметь возможность проверить, насколько быстро работает ваша программа, не полагаясь на секундомер или любую подобную глупость. Хотя выполнение большинства головоломок Advent of Code может занять максимум несколько секунд, также очень легко использовать неоптимальное решение. Знание того, какие части вашей программы необходимы для достижения «хорошего» времени выполнения.

Может быть интересно использовать Advent of Code для изучения нового языка или цепочки инструментов, но, на мой взгляд, этот подход лучше подходит для вашей второй или последующей попытки принять участие. В течение первого года у вас будет достаточно проблем, просто пытаясь не отставать от головоломной части головоломок.

Что будет в 2023 году?

В 2023 году мы продолжим наш слабый канал «Пришествие кода» (теперь «Программирование головоломок») с еженедельными, а не ежедневными задачами. Это поможет многим членам нашей команды отточить свои навыки и попробовать разные вещи таким образом, чтобы подготовить их к Advent of Code в следующем году.

Мне нравится помогать моим товарищам по команде более глубоко изучать их ремесло, поэтому Advent of Code в этом году стал для меня отличным способом помочь работать вместе с ними и учиться у них.