Появление кода, или AoC, — хорошо известное событие среди инженеров-программистов. Он проводится ежегодно, участники решают серию головоломок по программированию каждый день с 1 по 25 декабря. Эти головоломки можно решить на любом языке программирования, что дает разработчикам всех уровней прекрасную возможность попрактиковаться в своих навыках и опробовать новые языки. Головоломка каждого дня основывается на предыдущей, создавая сложный и сплоченный опыт для участников.

В этом году мы (в Researchable) провели внутренний конкурс, в котором дополнительные баллы начислялись тем, кто решил головоломку на языке COBOL. COBOL, или Common Business-Oriented Language, — это язык программирования высокого уровня, разработанный для бизнес-приложений. Это был один из первых широко используемых языков программирования, и многие системы до сих пор полагаются на него.

Но это еще не все. Также были представлены решения на некоторых нетрадиционных языках, таких как HTML и CSS (да, именно HTML и CSS, а не JavaScript, не SCSS), а также COBOL. В этом сообщении блога мы покажем два интересных решения некоторых из первых проблем AoC — одно в HTML и CSS, а другое в COBOL.

Как не очень опытные разработчики COBOL, мы смогли решить только первые несколько головоломок в COBOL. Для более поздних мы использовали другие, хотя и классные, языки, такие как Rust, Ruby, GO и C++. Они не будут представлены в этом сообщении в блоге.

Решение AoC день 1a в HTML

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

Предварительная обработка ввода в HTML

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

4235
342
6564

234534
4234

В это:

<body>
  <div class=”block”>
    <div class=”num” style=” — hght: 4235px”></div>
    <div class=”num” style=” — hght: 342px”></div>
    <div class=”num” style=” — hght: 6564px”></div>
  </div>
  <div class=”block”>
    <div class=”num” style=” — hght: 234534px”></div>
    <div class=”num” style=” — hght: 4234px”></div>
  </div>
</body>

Для этого я буду использовать регулярные выражения в моем редакторе (vim), чтобы сначала заменить все пустые строки на ‹/div›‹div class="block"›

:%s/^$/<\/div><div class=”block”>/g

Затем я заменяю все числа на ‹div class="num" style=" — hght: __px"›‹/div›, заменяя нужные числа в знаках подчеркивания. Для этого я использовал следующую команду замены:

:%s/\(\d\+\)/<div class=”num” style=” — hght: \1px”><\/div>/g

Что осталось сделать, так это добавить остальную часть HTML в начало и конец файла, например, обернув все в ‹html›, ‹head› и ‹body›, и добавив начальный и конечный теги первого и последнего ‹ div class=”блочный› элемент. После этого выполняется этап предварительной обработки.

Использование CSS для «вычисления» решения

Если мы используем приведенный выше HTML-код без применения каких-либо стилей, мы получим все элементы div, расположенные друг под другом (как по умолчанию для элементов блочного уровня). Это почти то, что нам нужно, так как мы хотим, чтобы элементы div, которые появляются внутри каждого блока div, складывались по высоте. Однако мы не хотим складывать высоты всех элементов блока. Нас интересует только высота самого большого блочного элемента. Таким образом, мы размещаем каждый блок чисел так, чтобы они появлялись рядом друг с другом. Для этого мы даем их родителям (телу) гибкий класс, а также устанавливаем правильную высоту числовых элементов. Кроме того, мы следим за тем, чтобы высота всего росла в соответствии с его содержимым и не ограничивалась областью просмотра браузера:

<style>
  body {
    margin: 0px;
    display: flex;
    flex-direction: row;
    height: fit-content;
  }
  .block {
    height: fit-content;
  }
  .num {
    width: 1px;
    height: var( — hght);
  }
</style>

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

Решение AoC день 4 в COBOL

В день 4 задача AoC состоит в том, чтобы найти для каждой пары из 2 диапазонов чисел, перекрываются ли их домены или нет. Входные данные для этой головоломки имеют следующую форму:

5–8,6–7
24–54,8–14

.. многое другое ..

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

Предварительная обработка всех чисел для фиксированной ширины.

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

Обратите внимание, что на самом деле в строке 12 есть небольшая ошибка, из-за которой второй диапазон печатается с запятой вместо тире. Это не влияет на синтаксический анализ COBOL, поскольку COBOL предполагает, что каждое число находится в определенном месте, и игнорирует весь остальной текст. Кроме того, давайте забудем о том, что в том же количестве кода на Python я мог бы решить всю проблему :D

После запуска этого скрипта на входе результирующий предварительно обработанный ввод (должен) иметь следующую форму:

005–008,006–007
024–054,008–014

.. многое другое ..

Это намного проще читать в программе COBOL. Причина этого станет ясна позже.

Структура программ COBOL

Программы COBOL разделены на несколько разделов и разделов. Я точно не знаю, в чем разница между секцией и дивизией. Структура каждой программы COBOL начинается с идентификационного раздела, который содержит название программы, имя автора и другую полезную информацию, которую я не заполнил.

Следующий раздел — это раздел среды, который содержит разделы, описывающие поведение программы при вводе-выводе. Здесь я указываю, что моя программа читает стандартный ввод. Далее идет разделение данных, в котором указывается структура памяти программы. Здесь должна быть объявлена ​​каждая переменная, используемая в программе. Кроме того, здесь описаны файловые дескрипторы, благодаря которым определяется структура файла, который будет прочитан. Важно, чтобы это было известно во время компиляции (чего мы не знали для необработанного ввода). Таким образом, эти разделы вместе выглядят следующим образом:

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

Структура памяти нашей программы

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

Это примерно означает, что каждая строка входного файла STUDENT-FILE (наш стандартный ввод) будет содержать PIC 9(3) (три цифры), за которым следует один PIC X (текстовый символ), за которым следуют 3 цифры, текстовый символ, 3 цифры, еще один символ и еще 3 цифры. Это точно соответствует одному из входных данных 123–456, 234–567, которые были созданы ранее. Я также создаю переменную с именем WS-LINE, которая имеет точно такую ​​же структуру, за исключением того, что на этот раз эта структура описывает переменную, а не файловый дескриптор. В дополнение к объявлению WS-LINE я также объявляю WS-COUNT (COUNT рабочей памяти), WS-EOF (это флаг, определяющий, достигли ли входные данные EOF (конец файла), и WS-SURFACETOTAL, который является вспомогательным переменная для части 2.

Сама программа

Сама программа состоит из процедуры, которая повторяется до тех пор, пока мы не достигнем EOF, после чего печатается простой счет. Это написано с использованием следующего кода COBOL:

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

Интересная часть COBOL — это оператор READ, который может выполнять разные действия в зависимости от того, достигнут ли конец дескриптора файла, из которого выполняется чтение. В этом случае в конце файла флаг WS-EOF устанавливается в «Y» (да), что приводит к завершению цикла. Если мы еще не достигли конца файла, вызывается процедура CountOverlap (которая фактически является реализацией для части 2). Кроме того, обратите внимание, что считываемая строка будет записана в переменную WS-LINE, где она будет деструктурирована на наши 4 числа и 3 символа-разделителя (заполнителя).

Подсчет включенных пар

В части 1 этого AoC мы будем подсчитывать количество пар диапазонов, которые входят друг в друга. Чтобы сделать это в нашей программе COBOL, мы просто используем несколько операторов if для наших четырех чисел, чтобы увидеть, входит ли один диапазон в другой.

Обратите внимание, что в COBOL нет оператора else-if, поэтому мне пришлось вложить еще один оператор IF внутрь ветки else. Позже я узнал, что в COBOL есть оператор EVALUATE, который может принимать несколько ветвей (например, оператор switch в современных языках).

Подсчет перекрывающихся диапазонов

Во второй части нам нужно подсчитать количество диапазонов, которые просто немного перекрываются с другим диапазоном. Это можно решить разными способами, но я не хотел слишком много думать обо всех проверках границ. Вместо этого я прибегнул к методу, используемому в доказательствах, который называется принципом ящичка. Я рассчитал общий размер диапазона от самой низкой нижней границы до самой высокой верхней границы и сравнил его с размером двух отдельных диапазонов. Если два отдельных диапазона не помещаются в общую площадь внешнего диапазона, это означает, что они «разделяют» некоторое пространство, то есть перекрываются. Чтобы сделать это «элегантно», я использую функции MIN и MAX. На обычном языке программирования это выглядело бы примерно так: поверхность = max(num2, num4) — min(num1, num3) + 1. Однако в COBOL это выглядит следующим чудовищем:

К сожалению, любой вызов функции должен иметь префикс FUNCTION. Обратите внимание, что +1 здесь, потому что мы имеем дело с закрытыми интервалами, а диапазон [6, 6] покрывает 1 пробел. По той же причине расчет размера отдельного диапазона имеет +1 для каждого диапазона (то есть +2).

Также заметьте, что я еще не научился писать функции на COBOL (даже не знаю, существуют ли они), поэтому об использовании операторов return не может быть и речи. Вместо этого все делается путем изменения глобальных переменных и обеспечения их инициализации во что-то разумное перед их использованием.