Это часть моей серии статей о Пришествии Clojure.

Головоломка из Дня 2 гласила:

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

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

Например, при наличии следующей электронной таблицы:

5 1 9 5
7 5 3
2 4 6 8

– Наибольшее и наименьшее значения первой строки – 9 и 1, а их разница – 8.
 – Наибольшее и наименьшее значения второй строки – 7 и 3, а их разница – 4.
 – Третья строка разница 6.
- В этом примере контрольная сумма электронной таблицы будет 8 + 4 + 6 = 18.

Какова контрольная сумма электронной таблицы во входных данных головоломки?

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

Учитывая последовательность чисел, мы хотим получить максимальное значение, минимальное значение и найти разницу. Функции max и min принимают несколько параметров, а не последовательность, но последовательность может быть расширена до позиции параметров с помощью apply.

Если переменная с именем row содержит числа в строке, выражение для максимума будет (применить максимальную строку). Тогда вся контрольная сумма будет:

(- (apply max row) (apply min row))

Это может быть сопоставлено с каждой строкой ввода, а это означает, что его необходимо представить в виде функции. Самый простой способ — использовать синтаксис анонимной функции, заменив row на %. Это создает последовательность контрольных сумм, которые затем необходимо суммировать. Подобно max и min, эту последовательность можно передать функции + с помощью apply. Опять же, мы будем использовать макрос многопоточности ->>, чтобы эти операции можно было выполнять шаг за шагом.

Попытка этого через данные примера выглядит следующим образом:

Это здорово, так что теперь нам нужно преобразовать текстовый ввод в эти числовые последовательности. Прежде всего, ввод необходимо разбить на строки, что можно сделать с помощью clojure.string/split и регулярного выражения, которое соответствует новой строке. Мне нравится обращаться к пространству имен clojure.string, используя str, поэтому эта функция становится str/split. После создания строк числа в каждой строке можно снова разделить с помощью функции split, используя регулярное выражение, которое соответствует пробелам. Этот второй split необходимо применить через map к линиям, созданным первым split. Результатом является последовательность строк, где каждая строка представляет собой последовательность строк. Наконец, для этого потребуется синтаксический анализатор чисел, сопоставленный с линией, для создания необходимых чисел.

Поскольку функция Integer/parseInt является функцией взаимодействия Java, она не представлена ​​в виде значения функции, которое можно передать в map. Простая функция-оболочка позволяет передать ее таким образом, который определен в строке 1 ниже.

Строки создаются с помощью split в строке 4. Каждая строка разбивается на короткие строки путем отображения разделения на все строки в строке 5. Наконец, partial создает функцию, которая будет отображать read-int на последовательность этих коротких строк, и это отображается на все входные строки в строке 6.

Файл данных может быть прочитан с помощью slurp, а затем передан функции as-arrays для получения необходимых входных данных, после чего мы можем передать их функции day2.

Применительно к файлу данных, который мне дали, это дало мне результат 44670, что привело ко второй части головоломки:

"Отличная работа; похоже, мы все-таки на правильном пути. Вот звезда за ваши усилия». Однако программа кажется немного обеспокоенной. Могут ли программы волноваться?

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

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

Например, при наличии следующей электронной таблицы:

5 9 2 8
9 4 7 3
3 8 6 5

В первой строке единственные два числа, которые делятся без остатка, — это 8 и 2; результат этого деления 4.

Во второй строке два числа: 9 и 3; результат 3.

В третьей строке результат равен 2.

В этом примере сумма результатов будет 4 + 3 + 2 = 9.

Какова сумма результатов каждой строки во входных данных головоломки?

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

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

Строка 2 выполняет сортировку, используя функцию «больше чем» > для обеспечения сравнений, тем самым сортируя от большего к меньшему. Строка 3 запускает цикл, который просматривает числа в строке и использует деструктурирование, чтобы получить первое и наибольшее число, f, и все остальные числа, r.

Линия 4 — это просто охранник, который проверяет, не нужно ли что-то искать. Это потерпит неудачу только в том случае, если в строке ничего не будет найдено, чего не должно происходить, но код всегда должен проверять данные, которые не ожидались.

Строка 5 пытается разделить оставшиеся числа на f. Функция some применяет свою анонимную функцию к каждому из чисел в r, возвращая первый ненулевой результат. Давайте быстро взглянем на эту анонимную функцию:

#(when (zero? (mod f %)) (/ f %))

Это проверяет, делится ли проверяемое число (представленное %) на f путем поиска mod, равного 0. Если оно подходит равномерно, верните это число, разделенное на f. Если не подходит, то выражение when возвращает nil. Таким образом, вызов some в строке 5 либо вернет искомое число, либо выполнение упадет до recur, который затем вернется к проверке последовательности, начиная со следующего по величине числа.

Вторая функция day2 будет идентична первой, только будет использовать эту контрольную сумму вместо анонимной функции, используемой в первой части головоломки.

Если бы это был производственный код, у меня была бы одна функция day2, которой передавалась бы функция контрольной суммы для использования, но это просто чрезмерная инженерия, когда у меня уже есть ответ.

Мой ответ был 285.

Окончательный код здесь:



Далее идет День 3.