Mi dispiace gente, nessuna esagonia questa volta ...
Il conteggio dei byte presuppone la codifica 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
Si aspetta la stringa target sulla prima riga e l'esagono sulla seconda riga dell'input. Stampa 0
o di 1
conseguenza.
Provalo online! (La prima riga abilita una suite di test, in cui ogni riga è un caso di test, utilizzando ¦
per la separazione anziché un avanzamento riga.)
Il modo corretto di risolvere questa sfida è ovviamente con una regex. ;) E se non fosse per il fatto che questa sfida coinvolge anche il procedura di spiegamento dell'esagono , questa risposta in realtà consisterebbe in nient'altro che un singolo regex lungo ~ 600 byte.
Questo non è ancora perfettamente ottimizzato, ma sono abbastanza soddisfatto del risultato (la mia prima versione funzionante, dopo aver rimosso i gruppi nominati e altre cose necessarie per il buonsenso, era di circa 1000 byte). Penso che potrei risparmiare circa 10 byte scambiando l'ordine della stringa e dell'esagono, ma alla fine richiederebbe una riscrittura completa della regex, che non mi sento in questo momento. C'è anche un risparmio di 2 byte omettendo ilG
stage, ma rallenta considerevolmente la soluzione, quindi aspetterò di apportare tale modifica fino a quando non sarò sicuro di aver giocato a golf come posso.
Spiegazione
La parte principale di questa soluzione fa ampio uso di gruppi di bilanciamento , quindi ti consiglio di leggerli, se vuoi capire come funziona in dettaglio (non ti biasimerò se non ...).
La prima parte della soluzione (cioè tutto tranne le ultime due righe) è una versione modificata della mia risposta a Unfolding the Hexagony source code . Costruisce l'esagono, lasciando intatta la stringa del bersaglio (e in realtà costruisce l'esagono prima della stringa del bersaglio). Ho apportato alcune modifiche al codice precedente per salvare i byte:
- Il carattere di sfondo è
×
invece di uno spazio in modo che non sia in conflitto con potenziali spazi nell'input.
- Il carattere no-op / jolly è
_
invece .
, in modo che le celle della griglia possano essere identificate in modo affidabile come caratteri di parole.
- Non inserisco spazi o rientranze dopo che l'esagono è stato costruito per la prima volta. Questo mi dà un esagono inclinato, ma in realtà è molto più conveniente lavorare con e le regole di adiacenza sono abbastanza semplici.
Ecco un esempio Per il seguente caso di prova:
ja
abcdefghij
Noi abbiamo:
××abc
×defg
hij__
____×
___××
ja
Confronta questo con il solito layout dell'esagono:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Ora possiamo vedere che i vicini sono tutti i soliti 8 vicini Moore, tranne i vicini nord-ovest e sud-est. Quindi dobbiamo controllare l'adiacenza orizzontale, verticale e sud-ovest / nord-est (bene e poi ci sono i bordi di avvolgimento). L'uso di questo layout più compatto ha anche il vantaggio che saremo in grado di usarli××
alla fine per determinare al volo le dimensioni dell'esagono quando ne abbiamo bisogno.
Dopo che questo modulo è stato creato, apportiamo un'altra modifica all'intera stringa:
T`d`À-É
Questo sostituisce le cifre con le lettere ASCII estese
ÀÁÂÃÄÅÆÇÈÉ
Poiché sono sostituiti sia nell'esagono che nella stringa target, ciò non influirà sul fatto che la stringa sia abbinata o meno. Inoltre, poiché sono lettere \w
e le \b
identificano ancora come celle esagonali. Il vantaggio di fare questa sostituzione è che ora possiamo usare \D
nella regex imminente per abbinare qualsiasi personaggio (in particolare, avanzamenti di riga e caratteri non di avanzamento di riga). Non possiamo usare l' s
opzione per farlo, perché dovremo .
abbinare i caratteri senza avanzamento di riga in più punti.
Ora l'ultimo bit: determinare se qualsiasi percorso corrisponde alla nostra stringa specificata. Questo viene fatto con una sola regex mostruosa. Potresti chiederti perché?!?! Bene, questo è fondamentalmente un problema di backtracking: si inizia da qualche parte e si tenta un percorso purché corrisponda alla stringa, e una volta che non lo fa si torna indietro e si tenta un vicino diverso dall'ultimo personaggio che ha funzionato. L' unica cosache si ottiene gratuitamente quando si lavora con regex è un backtracking. È letteralmente l'unica cosa che fa il motore regex. Quindi se troviamo solo un modo per descrivere un percorso valido (che è abbastanza complicato per questo tipo di problema, ma sicuramente possibile con i gruppi di bilanciamento), allora il motore regex risolverà trovando quel percorso tra tutti i possibili per noi. Sarebbe certamente possibile implementare la ricerca manualmente con più fasi ( e l'ho già fatto in passato ), ma dubito che sarebbe più breve in questo caso particolare.
Un problema con l'implementazione di questo con una regex è che non possiamo tessere arbitrariamente il cursore del motore regex avanti e indietro attraverso la stringa durante il backtracking (di cui avremmo bisogno poiché il percorso potrebbe andare su o giù). Quindi, invece, teniamo traccia del nostro "cursore" in un gruppo di acquisizione e lo aggiorniamo ad ogni passo (possiamo spostarci temporaneamente nella posizione del cursore con uno sguardo). Questo ci consente anche di memorizzare tutte le posizioni passate che utilizzeremo per verificare che non abbiamo già visitato la posizione corrente.
Quindi andiamo a questo. Ecco una versione leggermente più sana del regex, con gruppi denominati, rientro, ordine meno casuale dei vicini e alcuni commenti:
\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
Spero che l'idea generale sia approssimativamente chiara da questo. Come esempio di come funziona uno di quei movimenti lungo il percorso, diamo un'occhiata al bit che sposta il cursore verso sud:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Ricorda che i lookbehinds dovrebbero essere letti da destra a sinistra (o dal basso verso l'alto), perché è l'ordine in cui vengono eseguiti:
(?<=
(?<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).
)
Si noti che non è necessario mettere un ancoraggio davanti al \k<pos>
per assicurarsi che ciò raggiunga effettivamente l'inizio della stringa. <pos>
inizia sempre con una quantità ×
che non può essere trovata altrove, quindi questo agisce già come un'ancora implicita.
Non voglio gonfiare questo post più del necessario, quindi non entrerò negli altri 11 casi in dettaglio, ma in linea di principio funzionano tutti in modo simile. Controlliamo che <next>
può essere trovato in una direzione specifica (ammissibile) dalla vecchia posizione del cursore con l'aiuto di gruppi di bilanciamento, quindi memorizziamo la stringa fino a quella corrispondenza come nuova posizione del cursore <pos>
.