Наступил восьмой день Пришествия кода. Сегодня мы будем распутывать запутанные провода в стиле судоку, используя битовые манипуляции.
Как всегда, пожалуйста, попробуйте решить проблему, прежде чем искать решение. Для всех статей в этой серии ознакомьтесь с этим списком.
День8: Часть 1
Наша цель на сегодня — расшифровать зашифрованный семисегментный дисплей. Наши входные данные представляют собой серию строк, описывающих, какие сегменты дисплея горят:
aaa aaa aaa b c c b b c c b ddd ddd e f f e f e f f e f ggg ggg
Это отображение 876
будет закодировано как abcdefg acf abdefg
. К сожалению, помимо работы с этим представлением, наш ввод также зашифрован. К счастью, скремблирование стабильно внутри каждой строки ввода, и все дисплеи для одного ввода зашифровываются одинаково. Весь ввод представляет собой серию строк в следующем формате:
Отображения перед разделителем служат информацией, которая должна позволить нам расшифровать скремблирование, отображения после разделителя — это те, которые мы стремимся расшифровать.
Для части 1 мы не будем решать всю проблему расшифровки ввода. Вместо этого нам нужно определить только уникальные номера. Это 1, 4, 7 и 8, потому что мы можем идентифицировать эти числа, основываясь только на количестве освещенных сегментов (2 сегмента для 1, 4 сегмента для 4, 3 сегмента для 7 и 7 сегментов для 8).
Однако даже с этой более простой задачей мы уже можем подготовиться к нашей предстоящей задаче декодирования ввода и удобного представления семисегментного дисплея:
Биты — это удобный способ выражения элементов, состоящих из элементов с двумя состояниями (включено/выключено). Однако нам нужно быть осторожными, потому что манипулирование битами может легко сделать код трудным для чтения. Одним из способов смягчения этой проблемы является подготовка именованных констант, с которыми легче работать, т. е. здесь мы назвали каждый сегмент и подготовили массив, представляющий освещенные сегменты для каждой из цифр.
Когда подготовка завершена, теперь мы можем проанализировать ввод, сначала тест:
И затем код разбора:
На этот раз мы не полагаемся на потоки и вместо этого анализируем строку символ за символом. Мы читаем символы нижнего регистра, пропускаем пробелы и определяем, когда мы передаем разделитель |
. Выражение *it-'a'
основано на том факте, что символы нижнего регистра кодируются как последовательный диапазон, поэтому 'c'-'a'==2
и 'a'+3=='d'
.
После завершения синтаксического анализа ввода мы можем теперь прочитать весь ввод и использовать std::ranges::count_if
для подсчета экземпляров одного, четырех, семи и восьми.
Чтобы подсчитать количество освещенных сегментов, мы используем std::popcount
, который возвращает количество битов, установленных в 1.
День8: Часть 2
Время расшифровать входы. Мы будем использовать рудиментарную версию решения ограничений, чтобы определить, какой вход подключен к какому сегменту.
Решить проводку будет использовать логику, чтобы сузить возможные соединения. После того, как мы решили, какой вход подключен к какому сегменту, мы можем использовать эту информацию для декодирования числа (во входе после разделителя).
Когда тест готов, пришло время написать решатель. Нам понадобятся доменные переменные. Вы можете думать о них как о переменных с набором возможных значений. Смысл решения состоит в том, чтобы удалить невозможные значения из этих наборов, пока мы не достигнем единственного уникального решения. Наборы снова очень удобно представлять битами, поэтому мы используем uint8_t
для представления набора из семи возможных соединений сегментов.
Давайте посмотрим, какую информацию мы можем извлечь из зашифрованного ввода.
Очевидное
Мы уже говорили о числах, которые имеют уникальные представления. Один, четыре, семь и восемь. Восьмерка не дает нам никакой информации, поскольку перечисляет все сегменты.
- для одного освещенные сегменты должны быть либо C, либо F
- для четырех освещенные сегменты должны быть либо B, C, D или F
- для семи освещенные сегменты должны быть либо A, C или F
Отрицательный
Можем рассмотреть и негатив.
- во-первых, неосвещенные сегменты не могут быть C или F
- для четырех неосвещенные сегменты не могут быть B, C, D или F
- для семи несветящиеся сегменты не могут быть A, C или F
Есть и немного скрытый негатив.
- для чисел с пятью светящимися сегментами неосвещенными сегментами являются B, C, E или F.
- для чисел с шестью светящимися сегментами неосвещенными сегментами являются C, D или E.
Уборка
Наконец, если у нас есть провод только с одним соединением, этот провод фиксирован, и мы знаем, что никакой другой провод не подключен к этому сегменту.
Мы инициализируем каждый провод (переменная домена) для подключения ко всем сегментам. Затем мы просматриваем каждый из наших входов и применяем указанную логику в зависимости от количества освещенных сегментов. Мы перебирали все провода и удаляли невозможные соединения. Наконец, мы делаем очистку (строки 50–59).
После этого мы ожидаем, что каждый провод будет подключен к одному сегменту, и выполним процесс перевода в функции декодирования.
После всей проделанной работы нам нужно только перебрать числа, чтобы вычислить сумму:
Ссылки и технические примечания
Репозиторий ежедневных решений доступен по адресу: https://github.com/HappyCerberus/moderncpp-aoc-2021.
Посмотрите в этом списке статьи о других днях появления кода.
И, пожалуйста, не забудьте попробовать Пришествие кода на себе.
Спасибо за чтение
Спасибо, что прочитали эту статью. Вам понравилось?
Я также публикую видео на YouTube. У тебя есть вопросы? Напишите мне в Twitter или LinkedIn.