Извините, ребята, на этот раз нет гексагонии ...
Число байтов предполагает кодировку ISO 8859-1.
.+¶
$.'$*_¶$&
^_¶
¶
((^_|\2_)*)_\1{5}_+
$2_
^_*
$.&$*×_$&$&$.&$*×
M!&m`(?<=(?=×*(_)+)\A.*)(?<-1>.)+(?(1)!)|^.*$
O$`(_)|.(?=.*$)
$1
G-2`
T`d`À-É
m`\A(\D*)(?(_)\D*¶.|(.)\D*¶\2)((.)(?<=(?<4>_)\D+)?((?<=(?<1>\1.)\4\D*)|(?<=(?<1>\D*)\4(?<=\1)\D*)|(?<=\1(.(.)*¶\D*))((?<=(?<1>\D*)\4(?>(?<-7>.)*)¶.*\6)|(?<=(?<1>\D*)(?=\4)(?>(?<-7>.)+)¶.*\6))|(?<=(×)*¶.*)((?<=(?<1>\1.(?>(?<-9>¶.*)*))^\4\D*)|(?<=(?<1>\D*)\4(?>(?<-9>¶.*)*)(?<=\1)^\D*)|(?<=(?<1>\1\b.*(?(9)!)(?<-9>¶.*)*)\4×*¶\D*)|(?<=(?<1>\D*\b)\4.*(?(9)!)(?<-9>¶.*)*(?<=\1.)\b\D*))|(?<=(?<1>\1.(?>(?<-11>.)*)¶.*)\4(.)*¶\D*)|(?<=(?<1>\1(?>(?<-12>.)*)¶.*)\4(.)*¶\D*)|(?<=(?<1>\1.(?>(?<-13>.)*¶\D*))\4(\w)*\W+.+)|(?<=(?<1>.*)\4(?>(?<-14>.)*¶\D*)(?<=\1.)(\w)*\W+.+))(?<=\1(\D*).+)(?<!\1\15.*(?<-1>.)+))*\Z
Ожидается целевая строка в первой строке и шестиугольник во второй строке ввода. Отпечатки 0
или 1
соответственно.
Попробуйте онлайн! (Первая строка включает набор тестов, где каждая строка представляет собой тестовый пример, использующий ¦
для разделения вместо перевода строки.)
Правильный способ решить эту проблему, конечно, с помощью регулярных выражений. ;) И если бы не тот факт, что этот вызов также включает в себя процедуру разворачивания шестиугольника , этот ответ на самом деле состоял бы из одного регулярного выражения длиной ~ 600 байт.
Это пока не совсем оптимально, но я вполне доволен результатом (моя первая рабочая версия, после удаления именованных групп и других вещей, необходимых для здравомыслия, составила около 1000 байт). Я думаю, что я мог бы сэкономить около 10 байтов, меняя порядок строки и шестиугольника, но это потребовало бы полного переписывания регулярного выражения в конце, чего я сейчас не чувствую. Существует также 2-байтовая экономия за счет пропуска G
сцены, но это значительно замедляет решение, поэтому я подожду с внесением этого изменения, пока не буду уверен, что играю в гольф так хорошо, как могу.
объяснение
Основная часть этого решения широко использует балансировочные группы , поэтому я рекомендую ознакомиться с ними, если вы хотите понять, как это работает в деталях (я не буду винить вас, если вы не ...).
Первая часть решения (т. Е. Все, кроме двух последних строк) является модифицированной версией моего ответа на Unfolding the Hexagony source code . Он создает шестиугольник, оставляя целевую строку нетронутой (и фактически создает шестиугольник перед целевой строкой). Я внес некоторые изменения в предыдущий код для сохранения байтов:
- Фоновый символ
×
вместо пробела, чтобы он не конфликтовал с потенциальными пробелами на входе.
_
Вместо этого .
используется подстановочный / подстановочный знак , поэтому ячейки сетки могут быть надежно идентифицированы как символы слова.
- Я не вставляю никаких пробелов или отступов после того, как шестиугольник сначала построен. Это дает мне наклонный шестиугольник, но на самом деле с ним гораздо удобнее работать, а правила смежности довольно просты.
Вот пример. Для следующего теста:
ja
abcdefghij
Мы получаем:
××abc
×defg
hij__
____×
___××
ja
Сравните это с обычным расположением шестиугольника:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Мы можем видеть, что соседи теперь все обычные соседи Мура, за исключением северо-западных и юго-восточных соседей. Таким образом, мы должны проверить горизонтальную, вертикальную и смежность с юго-западом / северо-востоком (ну, а затем есть края обтекания). Использование этой более компактной компоновки также дает бонус, который мы сможем использовать ××
в конце, чтобы определить размер шестиугольника на лету, когда он нам понадобится.
После того, как эта форма была построена, мы вносим еще одно изменение во всю строку:
T`d`À-É
Это заменяет цифры расширенными буквами ASCII
ÀÁÂÃÄÅÆÇÈÉ
Поскольку они заменяются как в шестиугольнике, так и в целевой строке, это не повлияет на соответствие строки или нет. Кроме того, поскольку они являются буквами \w
и \b
до сих пор идентифицируют их как ячейки шестиугольника. Преимущество этой замены состоит в том, что теперь мы можем использовать \D
в предстоящем регулярном выражении соответствие любому символу (в частности, символам перевода строки, а также символам перевода строки). Мы не можем использовать эту s
опцию, чтобы выполнить это, потому что нам нужно .
сопоставлять символы, не являющиеся переводом строки, в нескольких местах.
Теперь последний бит: определение, соответствует ли какой-либо путь нашей заданной строке. Это делается с помощью одного чудовищного регулярного выражения. Вы можете спросить себя, почему?!?! Что ж, по сути это проблема с возвратом: вы начинаете где-нибудь и пытаетесь найти путь, пока он совпадает со строкой, и если он не возвращается, вы пытаетесь найти соседа, отличного от последнего сработавшего символа. Одна вещьто, что вы получаете бесплатно при работе с регулярным выражением, является возвращением. Это буквально единственное, что делает движок регулярных выражений. Поэтому, если мы просто найдем способ описать действительный путь (который достаточно сложен для такого рода проблем, но определенно возможен с балансировкой групп), то механизм регулярных выражений будет разбираться в том, чтобы найти этот путь среди всех возможных для нас. Конечно, было бы возможно реализовать поиск вручную в несколько этапов ( и я делал это в прошлом ), но я сомневаюсь, что в данном конкретном случае это будет короче.
Одна из проблем, связанных с реализацией этого с помощью регулярного выражения, заключается в том, что мы не можем произвольно переплетать курсор движка регулярного выражения назад и вперед через строку во время обратного отслеживания (что нам понадобится, поскольку путь может идти вверх или вниз). Таким образом, вместо этого мы отслеживаем наш собственный «курсор» в группе захвата и обновляем его на каждом шаге (мы можем временно перейти к позиции курсора с помощью обходного пути). Это также позволяет нам сохранять все прошлые позиции, которые мы будем использовать для проверки того, что мы не посещали текущую позицию раньше.
Итак, давайте вернемся к этому. Вот немного более разумная версия регулярного выражения с именованными группами, отступами, менее случайным порядком соседей и некоторыми комментариями:
\A
# Store initial cursor position in <pos>
(?<pos>\D*)
(?(_)
# If we start on a wildcard, just skip to the first character of the target.
\D*¶.
|
# Otherwise, make sure that the target starts with this character.
(?<first>.)\D*¶\k<first>
)
(?:
# Match 0 or more subsequent characters by moving the cursor along the path.
# First, we store the character to be matched in <next>.
(?<next>.)
# Now we optionally push an underscore on top (if one exists in the string).
# Depending on whether this done or not (both of which are attempted by
# the engine's backtracking), either the exact character, or an underscore
# will respond to the match. So when we now use the backreference \k<next>
# further down, it will automatically handle wildcards correctly.
(?<=(?<next>_)\D+)?
# This alternation now simply covers all 6 possible neighbours as well as
# all 6 possible wrapped edges.
# Each option needs to go into a separate lookbehind, because otherwise
# the engine would not backtrack through all possible neighbours once it
# has found a valid one (lookarounds are atomic).
# In any case, if the new character is found in the given direction, <pos>
# will have been updated with the new cursor position.
(?:
# Try moving east.
(?<=(?<pos>\k<pos>.)\k<next>\D*)
|
# Try moving west.
(?<=(?<pos>\D*)\k<next>(?<=\k<pos>)\D*)
|
# Store the horizontal position of the cursor in <x> and remember where
# it is (because we'll need this for the next two options).
(?<=\k<pos>(?<skip>.(?<x>.)*¶\D*))
(?:
# Try moving north.
(?<=(?<pos>\D*)\k<next>(?>(?<-x>.)*)¶.*\k<skip>)
|
# Try moving north-east.
(?<=(?<pos>\D*)(?=\k<next>)(?>(?<-x>.)+)¶.*\k<skip>)
)
|
# Try moving south.
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
|
# Try moving south-east.
(?<=(?<pos>\k<pos>(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
|
# Store the number of '×' at the end in <w>, which is one less than the
# the side-length of the hexagon. This happens to be the number of lines
# we need to skip when wrapping around certain edges.
(?<=(?<w>×)*¶.*)
(?:
# Try wrapping around the east edge.
(?<=(?<pos>\k<pos>.(?>(?<-w>¶.*)*))^\k<next>\D*)
|
# Try wrapping around the west edge.
(?<=(?<pos>\D*)\k<next>(?>(?<-w>¶.*)*)(?<=\k<pos>)^\D*)
|
# Try wrapping around the south-east edge.
(?<=(?<pos>\k<pos>\b.*(?(w)!)(?<-w>¶.*)*)\k<next>×*¶\D*)
|
# Try wrapping around the north-west edge.
(?<=(?<pos>\D*\b)\k<next>.*(?(w)!)(?<-w>¶.*)*(?<=\k<pos>.)\b\D*)
)
|
# Try wrapping around the south edge.
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*¶\D*))\k<next>(?<x>\w)*\W+.+)
|
# Try wrapping around the north edge.
(?<=(?<pos>.*)\k<next>(?>(?<-x>.)*¶\D*)(?<=\k<pos>.)(?<x>\w)*\W+.+)
)
# Copy the current cursor position into <current>.
(?<=\k<pos>(?<current>\D*).+)
# Make sure that no matter how many strings we pop from our stack of previous
# cursor positions, none are equal to the current one (to ensure that we use
# each cell at most once).
(?<!\k<pos>\k<current>.*(?<-pos>.)+)
)*
# Finally make sure that we've reached the end of the string, so that we've
# successfully matched all characters in the target string.
\Z
Я надеюсь, что общая идея примерно ясна из этого. В качестве примера того, как работает одно из этих движений вдоль пути, давайте рассмотрим бит, который перемещает курсор на юг:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Помните, что lookbehinds следует читать справа налево (или снизу вверх), потому что это порядок, в котором они выполняются:
(?<=
(?<pos>
\k<pos> # Check that this is the old cursor position.
. # Match the character directly on top of the new one.
(?>(?<-x>.)*) # Match the same amount of characters as before.
¶.* # Skip to the next line (the line, the old cursor is on).
) # We will store everything left of here as the new
# cursor position.
\k<next> # ...up to a match of our current target character.
(?<x>.)* # Count how many characters there are...
¶\D* # Skip to the end of some line (this will be the line below
# the current cursor, which the regex engine's backtracking
# will determine for us).
)
Обратите внимание, что нет необходимости ставить якорь перед, \k<pos>
чтобы убедиться, что он действительно достигает начала строки. <pos>
всегда начинается с количества, ×
которое не может быть найдено где-либо еще, так что это уже действует как неявная привязка.
Я не хочу раздувать этот пост больше, чем необходимо, поэтому я не буду вдаваться в подробности остальных 11 случаев, но в принципе все они работают одинаково. Мы проверяем, что <next>
можно найти в каком-то определенном (допустимом) направлении от старой позиции курсора с помощью уравновешивающих групп, а затем сохраняем строку до этого соответствия в качестве новой позиции курсора в <pos>
.