Retina , 744 octets
Désolé les gars, pas d'Hexagonie cette fois ...
Le nombre d'octets suppose un codage 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
Attend la chaîne cible sur la première ligne et l'hexagone sur la deuxième ligne de l'entrée. Imprime 0
ou en 1
conséquence.
Essayez-le en ligne! (La première ligne active une suite de tests, où chaque ligne est un scénario de test et utilise ¦
pour la séparation au lieu d'un saut de ligne.)
La bonne façon de résoudre ce défi est bien sûr avec une expression régulière. ;) Et si ce n’était pas le cas, ce défi impliquait également la procédure de déploiement de l’hexagone , cette réponse ne serait en réalité constituée que d’une simple expression rationnelle longue de 600 octets.
Le jeu n’est pas encore optimal, mais je suis assez satisfait du résultat (ma première version opérationnelle, après avoir supprimé les groupes nommés et d’autres éléments nécessaires à la santé mentale, était d’environ 1000 octets). Je pense que je pourrais économiser environ 10 octets en inversant l'ordre de la chaîne et de l'hexagone, mais cela nécessiterait une réécriture complète de la regex à la fin, ce que je ne me sens pas à la hauteur. En omettant l' G
étape, on économise également 2 octets , mais cela ralentit considérablement la solution. J'attendrai donc de faire ce changement jusqu'à ce que je sois sûr d'avoir joué au golf autant que je peux.
Explication
La partie principale de cette solution utilise énormément les groupes d'équilibrage . Je vous recommande donc de les lire si vous voulez comprendre comment cela fonctionne en détail (je ne vous blâmerai pas si vous ne le faites pas ...).
La première partie de la solution (c'est-à-dire tout sauf les deux dernières lignes) est une version modifiée de ma réponse à Unfolding le code source Hexagony . Il construit l'hexagone, tout en laissant la chaîne cible intacte (et construit en fait l'hexagone avant la chaîne cible). J'ai apporté quelques modifications au code précédent pour économiser des octets:
- Le caractère d'arrière-plan
×
remplace un espace afin d'éviter tout conflit avec des espaces potentiels dans l'entrée.
- Le caractère no-op / wildcard est à la
_
place .
, de sorte que les cellules de la grille peuvent être identifiées de manière fiable en tant que caractères de mot.
- Je n'insère pas d'espaces ni d'indentation après la construction initiale de l'hexagone. Cela me donne un hexagone incliné, mais c'est en fait beaucoup plus pratique et les règles de contiguïté sont assez simples.
Voici un exemple. Pour le cas de test suivant:
ja
abcdefghij
On a:
××abc
×defg
hij__
____×
___××
ja
Comparez cela avec la disposition habituelle de l'hexagone:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Nous pouvons voir que les voisins sont maintenant tous les voisins habituels de 8 Moore, à l'exception des voisins du nord-ouest et du sud-est. Nous devons donc vérifier la contiguïté horizontale, verticale et sud-ouest / nord-est (bon et puis il y a les bords enveloppants). L'utilisation de cette présentation plus compacte offre également l'avantage supplémentaire de pouvoir utiliser celles-ci ××
à la fin pour déterminer la taille de l'hexagone à la volée lorsque nous en avons besoin.
Une fois ce formulaire construit, nous apportons un changement supplémentaire à la chaîne entière:
T`d`À-É
Ceci remplace les chiffres par les lettres ASCII étendues
ÀÁÂÃÄÅÆÇÈÉ
Comme ils sont remplacés à la fois dans l'hexagone et dans la chaîne cible, cela n'aura aucune incidence sur le fait que la chaîne soit identique ou non. En outre, puisque ce sont des lettres \w
et les \b
identifient toujours comme des cellules hexagonales. L'avantage de cette substitution est que nous pouvons maintenant utiliser \D
dans la prochaine expression rationnelle pour associer n'importe quel caractère (en particulier les sauts de ligne ainsi que les caractères autres que les sauts de ligne). Nous ne pouvons pas utiliser l' s
option pour accomplir cela, car nous devrons .
faire correspondre des caractères sans saut de ligne à plusieurs endroits.
Maintenant le dernier bit: déterminer si un chemin correspond à notre chaîne donnée. Cela se fait avec une seule regex monstrueuse. Vous pourriez vous demander pourquoi?!?! Eh bien, c’est fondamentalement un problème de retour en arrière: vous commencez quelque part et essayez un chemin tant qu’il correspond à la chaîne, et une fois que cela ne vous retourne pas, vous essayez un voisin différent du dernier caractère qui a fonctionné. La seule choseque vous obtenez gratuitement lorsque vous travaillez avec regex est un retour en arrière. C'est littéralement la seule chose que fait le moteur regex. Donc, si nous trouvons simplement un moyen de décrire un chemin valide (ce qui est assez compliqué pour ce genre de problème, mais tout à fait possible avec des groupes d'équilibrage), le moteur des expressions rationnelles trouvera ce chemin parmi tous ceux qui sont possibles pour nous. Il serait certainement possible de mettre en œuvre la recherche manuellement avec plusieurs étapes ( et je l'ai déjà fait par le passé ), mais je doute que cela soit plus court dans ce cas particulier.
Un problème avec l'implémentation de cela avec une expression rationnelle est que nous ne pouvons pas tisser de manière arbitraire le curseur du moteur des expressions rationnelles dans la chaîne pendant le retour en arrière (ce dont nous aurions besoin, car le chemin pourrait monter ou descendre). Donc, au lieu de cela, nous gardons trace de notre propre "curseur" dans un groupe de capture et le mettons à jour à chaque étape (nous pouvons nous déplacer temporairement à la position du curseur avec un lookaround). Cela nous permet également de stocker toutes les positions passées que nous utiliserons pour vérifier que nous n'avons pas visité la position actuelle auparavant.
Alors allons-y. Voici une version légèrement plus saine de la regex, avec des groupes nommés, une indentation, un ordre moins aléatoire des voisins et quelques commentaires:
\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
J'espère que l'idée générale en ressort clairement. À titre d'exemple du fonctionnement de l'un de ces mouvements le long du chemin, examinons le bit qui déplace le curseur vers le sud:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Rappelez-vous que les regards noirs doivent être lus de droite à gauche (ou de bas en haut), car c'est l'ordre dans lequel ils sont exécutés:
(?<=
(?<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).
)
Notez qu'il n'est pas nécessaire de mettre une ancre devant le \k<pos>
pour s'assurer que cela atteint réellement le début de la chaîne. <pos>
commence toujours par une quantité de ce ×
qui ne peut être trouvé nulle part ailleurs, donc cela agit déjà comme une ancre implicite.
Je ne veux pas trop encaisser ce message, je ne vais donc pas entrer dans les 11 autres cas en détail, mais en principe, ils fonctionnent tous de la même manière. Nous vérifions qu’il <next>
peut être trouvé dans une direction spécifique (admissible) à partir de l’ancienne position du curseur à l’aide de groupes d’équilibrage, puis nous stockons la chaîne jusqu’à cette correspondance en tant que nouvelle position du curseur <pos>
.