Lo siento amigos, no Hexagony esta vez ...
El recuento de bytes asume la codificación 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 la cadena objetivo en la primera línea y el hexágono en la segunda línea de la entrada. Impresiones 0
o en 1
consecuencia.
Pruébalo en línea! (La primera línea habilita un conjunto de pruebas, donde cada línea es un caso de prueba, que se utiliza ¦
para la separación en lugar de un salto de línea).
La forma correcta de resolver este desafío es con una expresión regular, por supuesto. ;) Y si no fuera por el hecho de que este desafío también involucra el procedimiento de desarrollo del hexágono , esta respuesta en realidad consistiría en nada más que una simple expresión regular de ~ 600 bytes.
Esto todavía no está muy optimizado, pero estoy bastante contento con el resultado (mi primera versión de trabajo, después de eliminar los grupos con nombre y otras cosas necesarias para la cordura, era de alrededor de 1000 bytes). Creo que podría ahorrar unos 10 bytes intercambiando el orden de la cadena y el hexágono, pero requeriría una reescritura completa de la expresión regular al final, lo que no estoy sintiendo en este momento. También hay un ahorro de 2 bytes al omitir la G
etapa, pero ralentiza la solución considerablemente, por lo que esperaré haciendo ese cambio hasta que esté seguro de haber jugado al golf tan bien como pueda.
Explicación
La parte principal de esta solución hace un uso extensivo de los grupos de equilibrio , por lo que le recomiendo leerlos, si desea entender cómo funciona esto en detalle (no lo culparé si no lo hace ...).
La primera parte de la solución (es decir, todo excepto las dos últimas líneas) es una versión modificada de mi respuesta a Desplegar el código fuente de Hexagony . Construye el hexágono, mientras deja intacta la cadena objetivo (y en realidad construye el hexágono antes de la cadena objetivo). He realizado algunos cambios en el código anterior para guardar bytes:
- El carácter de fondo es en
×
lugar de un espacio para que no entre en conflicto con espacios potenciales en la entrada.
- El carácter no-op / comodín es
_
en cambio .
, de modo que las celdas de la cuadrícula se pueden identificar de forma fiable como caracteres de texto.
- No inserto ningún espacio ni sangría después de que se construye el hexágono por primera vez. Eso me da un hexágono inclinado, pero en realidad es mucho más conveniente trabajar con él y las reglas de adyacencia son bastante simples.
Aquí hay un ejemplo. Para el siguiente caso de prueba:
ja
abcdefghij
Obtenemos:
××abc
×defg
hij__
____×
___××
ja
Compare esto con el diseño habitual del hexágono:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Podemos ver que los vecinos ahora son todos los 8 vecinos habituales de Moore, excepto los vecinos del noroeste y sureste. Por lo tanto, debemos verificar la adyacencia horizontal, vertical y sur-oeste / noreste (bueno, y luego están los bordes de envoltura). El uso de este diseño más compacto también tiene la ventaja de que podremos usarlos ××
al final para determinar el tamaño del hexágono sobre la marcha cuando lo necesitemos.
Después de construir este formulario, hacemos un cambio más en toda la cadena:
T`d`À-É
Esto reemplaza los dígitos con las letras ASCII extendidas
ÀÁÂÃÄÅÆÇÈÉ
Dado que se reemplazan tanto en el hexágono como en la cadena objetivo, esto no afectará si la cadena coincide o no. Además, dado que son letras \w
y \b
aún las identifican como celdas hexagonales. El beneficio de hacer esta sustitución es que ahora podemos usar \D
en la próxima expresión regular para que coincida con cualquier carácter (específicamente, saltos de línea, así como caracteres sin salto de línea). No podemos usar la s
opción para lograr eso, porque necesitaremos .
hacer coincidir los caracteres sin salto de línea en varios lugares.
Ahora el último bit: determinar si alguna ruta coincide con nuestra cadena dada. Esto se hace con una sola expresión monstruosa. Te preguntarás por qué?!?! Bueno, esto es fundamentalmente un problema de retroceso: comienzas en algún lugar e intentas un camino siempre que coincida con la cadena, y una vez que no lo haces, retrocedes e intentas con un vecino diferente del último personaje que funcionó. La única cosaque obtienes gratis cuando trabajas con regex es un retroceso. Eso es literalmente lo único que hace el motor regex. Entonces, si solo encontramos una manera de describir una ruta válida (que es lo suficientemente complicada para este tipo de problema, pero definitivamente posible con grupos de equilibrio), entonces el motor de expresiones regulares resolverá encontrar esa ruta entre todos los posibles para nosotros. Ciertamente sería posible implementar la búsqueda manualmente con múltiples etapas ( y lo he hecho en el pasado ), pero dudo que sea más corto en este caso particular.
Un problema con la implementación de esto con una expresión regular es que no podemos tejer arbitrariamente el cursor del motor de expresión regular hacia adelante y hacia atrás a través de la cadena durante el retroceso (que necesitaríamos ya que la ruta podría subir o bajar). Entonces, en cambio, hacemos un seguimiento de nuestro propio "cursor" en un grupo de captura y lo actualizamos en cada paso (podemos movernos temporalmente a la posición del cursor con una búsqueda). Esto también nos permite almacenar todas las posiciones pasadas que usaremos para verificar que no hayamos visitado la posición actual antes.
Vamos a por ello. Aquí hay una versión un poco más sensata de la expresión regular, con grupos con nombre, sangría, un orden menos aleatorio de vecinos y algunos comentarios:
\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 la idea general sea más o menos clara a partir de esto. Como ejemplo de cómo funciona uno de esos movimientos a lo largo del camino, veamos el bit que mueve el cursor hacia el sur:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Recuerde que las retrospectivas deben leerse de derecha a izquierda (o de abajo hacia arriba), porque ese es el orden en que se ejecutan:
(?<=
(?<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).
)
Tenga en cuenta que no es necesario poner un ancla frente al \k<pos>
para asegurarse de que esto realmente llegue al comienzo de la cadena. <pos>
siempre comienza con una cantidad ×
que no se puede encontrar en ningún otro lugar, por lo que esto ya actúa como un ancla implícita.
No quiero hinchar esta publicación más de lo necesario, por lo que no entraré en los otros 11 casos en detalle, pero en principio todos funcionan de manera similar. Verificamos que <next>
se puede encontrar en alguna dirección específica (admisible) desde la posición del cursor anterior con la ayuda de grupos de equilibrio, y luego almacenamos la cadena hasta esa coincidencia como la nueva posición del cursor <pos>
.