Desculpe pessoal, não há Hexagony desta vez ...
A contagem de bytes assume a codificação 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
Espera a sequência de destino na primeira linha e o hexágono na segunda linha da entrada. Imprime 0
ou de 1
acordo.
Experimente online! (A primeira linha ativa um conjunto de testes, onde cada linha é um caso de teste, usando ¦
para separação em vez de um avanço de linha.)
A maneira correta de resolver esse desafio é com uma regex, é claro. ;) E se não fosse o fato de que esse desafio também envolve o procedimento de desdobramento do hexágono , essa resposta realmente consistiria em nada além de um único regex de ~ 600 bytes de comprimento.
Isso ainda não está bem otimizado, mas estou muito feliz com o resultado (minha primeira versão de trabalho, depois de remover grupos nomeados e outras coisas necessárias para a sanidade, ficou em torno de 1000 bytes). Eu acho que poderia economizar cerca de 10 bytes trocando a ordem da string e do hexágono, mas isso exigiria uma reescrita completa da regex no final, o que não estou me sentindo bem agora. Também há uma economia de 2 bytes ao se omitir o G
palco, mas isso diminui consideravelmente a solução, então esperarei fazendo essa alteração até ter certeza de que joguei isso da melhor maneira possível.
Explicação
A parte principal desta solução faz uso extensivo de grupos de balanceamento , por isso recomendo a leitura deles, se você quiser entender como isso funciona em detalhes (não vou culpá-lo se não o fizer ...).
A primeira parte da solução (ou seja, tudo, exceto as duas últimas linhas) é uma versão modificada da minha resposta para Desdobrar o código fonte do Hexagony . Ele constrói o hexágono, deixando a string de destino intocada (e na verdade constrói o hexágono antes da string de destino). Fiz algumas alterações no código anterior para salvar bytes:
- O caractere de plano de fundo é em
×
vez de um espaço, para que não entre em conflito com os espaços em potencial na entrada.
- O caractere no-op / curinga é , em
_
vez disso .
, para que as células da grade possam ser identificadas de maneira confiável como caracteres de palavra.
- Não insiro espaços ou recuo após a primeira construção do hexágono. Isso me dá um hexágono inclinado, mas na verdade é muito mais conveniente de se trabalhar e as regras de adjacência são bastante simples.
Aqui está um exemplo. Para o seguinte caso de teste:
ja
abcdefghij
Nós temos:
××abc
×defg
hij__
____×
___××
ja
Compare isso com o layout usual do hexágono:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Podemos ver que os vizinhos são agora todos os 8 vizinhos de Moore habituais, exceto os vizinhos noroeste e sudeste. Portanto, precisamos verificar a adjacência horizontal, vertical e sudoeste / nordeste (bem e depois existem as bordas da embalagem). Usar esse layout mais compacto também tem o bônus de poder usá-los ××
no final para determinar o tamanho do hexágono rapidamente quando precisarmos.
Depois que esse formulário foi construído, fazemos mais uma alteração na cadeia inteira:
T`d`À-É
Isso substitui os dígitos pelas letras ASCII estendidas
ÀÁÂÃÄÅÆÇÈÉ
Como eles são substituídos no hexágono e na sequência de destino, isso não afetará se a sequência é correspondida ou não. Além disso, como são letras \w
e \b
ainda as identificam como células hexagonais. O benefício de fazer essa substituição é que agora podemos usar \D
no próximo regex para corresponder a qualquer caractere (especificamente, feeds de linha e caracteres não feed de linha). Não podemos usar a s
opção para fazer isso, porque precisaremos .
corresponder caracteres que não sejam de avanço de linha em vários lugares.
Agora, o último bit: determinar se algum caminho corresponde à nossa string especificada. Isso é feito com um único regex monstruoso. Você pode se perguntar por quê?!?! Bem, isso é fundamentalmente um problema de retorno: você começa em algum lugar e tenta um caminho, desde que corresponda à string, e uma vez que não retorna, tenta um vizinho diferente do último caractere que funcionou. A única coisaque você recebe de graça quando trabalha com regex está retornando. Essa é literalmente a única coisa que o mecanismo regex faz. Portanto, se apenas encontrarmos uma maneira de descrever um caminho válido (o que é bastante complicado para esse tipo de problema, mas definitivamente possível com grupos de balanceamento), o mecanismo de expressão regular resolverá encontrar esse caminho entre todos os possíveis para nós. Certamente seria possível implementar a pesquisa manualmente com vários estágios ( e já fiz isso no passado ), mas duvido que seja mais curto nesse caso específico.
Um problema ao implementar isso com uma regex é que não podemos tecer arbitrariamente o cursor do mecanismo de regex para frente e para trás através da string durante o retrocesso (o que precisaríamos, pois o caminho pode subir ou descer). Então, em vez disso, mantemos o controle de nosso próprio "cursor" em um grupo de captura e o atualizamos a cada passo (podemos mover temporariamente para a posição do cursor com uma olhada). Isso também nos permite armazenar todas as posições anteriores que usaremos para verificar se não visitamos a posição atual antes.
Então vamos fazer isso. Aqui está uma versão um pouco mais saudável da regex, com grupos nomeados, indentação, ordem menos aleatória de vizinhos e alguns comentários:
\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
Espero que a idéia geral seja mais ou menos clara disso. Como um exemplo de como um desses movimentos ao longo do caminho funciona, vejamos o bit que move o cursor para o sul:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Lembre-se de que os lookbehinds devem ser lidos da direita para a esquerda (ou de baixo para cima), porque essa é a ordem em que são executados:
(?<=
(?<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).
)
Observe que não é necessário colocar uma âncora na frente do \k<pos>
para garantir que isso realmente chegue ao início da string. <pos>
sempre começa com uma quantidade ×
que não pode ser encontrada em nenhum outro lugar; portanto, isso já atua como uma âncora implícita.
Não quero inchar este post mais do que o necessário, por isso não entrarei nos outros 11 casos em detalhes, mas, em princípio, todos funcionam da mesma forma. Verificamos que isso <next>
pode ser encontrado em alguma direção específica (admissível) a partir da antiga posição do cursor com a ajuda de grupos de balanceamento e, em seguida, armazenamos a string até essa correspondência como a nova posição do cursor <pos>
.