Sorry Leute, diesmal kein Hexagony ...
Die Anzahl der Bytes setzt die Kodierung nach ISO 8859-1 voraus.
.+¶
$.'$*_¶$&
^_¶
¶
((^_|\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
Erwartet die Zielzeichenfolge in der ersten Zeile und das Sechseck in der zweiten Zeile der Eingabe. Druckt 0
oder 1
entsprechend.
Probieren Sie es online! (Die erste Zeile aktiviert eine Testsuite, bei der jede Zeile ein Testfall ist und ¦
für die Trennung anstelle eines Zeilenvorschubs verwendet wird.)
Der richtige Weg, um diese Herausforderung zu lösen, ist natürlich mit einem Regex. ;) Und ohne die Tatsache, dass diese Herausforderung auch das Entfalten des Sechsecks beinhaltet , würde diese Antwort eigentlich nur aus einem einzigen ~ 600 Byte langen regulären Ausdruck bestehen.
Dies ist noch nicht ganz optimal, aber ich bin mit dem Ergebnis ziemlich zufrieden (meine erste Arbeitsversion nach dem Entfernen von benannten Gruppen und anderem für die Vernunft erforderlichen Material war ungefähr 1000 Bytes). Ich denke, ich könnte ungefähr 10 Bytes einsparen, indem ich die Reihenfolge der Zeichenfolge und des Sechsecks vertausche, aber es würde am Ende ein vollständiges Umschreiben des regulären Ausdrucks erfordern, was ich derzeit nicht für richtig halte. Es gibt auch eine 2-Byte-Einsparung, wenn die G
Bühne weggelassen wird , aber dies verlangsamt die Lösung erheblich. Ich werde mit dieser Änderung warten, bis ich sicher bin, dass ich so gut wie möglich Golf gespielt habe.
Erläuterung
Der Hauptteil dieser Lösung verwendet in großem Umfang Bilanzkreise , daher empfehle ich, sie zu lesen, wenn Sie verstehen möchten, wie dies im Detail funktioniert (ich werde Ihnen keine Vorwürfe machen, wenn Sie dies nicht tun ...).
Der erste Teil der Lösung (dh alles außer den letzten beiden Zeilen) ist eine modifizierte Version meiner Antwort auf das Entfalten des Hexagony-Quellcodes . Es konstruiert das Sechseck, während die Zielzeichenfolge unberührt bleibt (und es konstruiert tatsächlich das Sechseck vor der Zielzeichenfolge). Ich habe einige Änderungen am vorherigen Code vorgenommen, um Bytes zu sparen:
- Das Hintergrundzeichen steht
×
anstelle eines Leerzeichens, damit es nicht zu Konflikten mit potenziellen Leerzeichen in der Eingabe kommt.
- Das No-Op / Wildcard-Zeichen ist
_
stattdessen .
, sodass Rasterzellen zuverlässig als Wortzeichen identifiziert werden können.
- Ich füge keine Leerzeichen oder Einrückungen ein, nachdem das Sechseck zum ersten Mal konstruiert wurde. Das gibt mir ein schräges Sechseck, aber das ist tatsächlich viel bequemer zu bearbeiten und die Adjazenzregeln sind ziemlich einfach.
Hier ist ein Beispiel. Für den folgenden Testfall:
ja
abcdefghij
Wir bekommen:
××abc
×defg
hij__
____×
___××
ja
Vergleichen Sie dies mit der üblichen Anordnung des Sechsecks:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Wir können sehen, dass die Nachbarn jetzt alle die üblichen 8 Moore-Nachbarn sind, mit Ausnahme der Nordwest- und Südostnachbarn. Wir müssen also die horizontale, vertikale und südwestliche / nordöstliche Nachbarschaft prüfen (und dann gibt es die Umhüllungskanten). Die Verwendung dieses kompakteren Layouts hat auch den Vorteil, dass wir diese ××
am Ende verwenden können, um die Größe des Sechsecks im Handumdrehen zu bestimmen, wenn wir es benötigen.
Nachdem dieses Formular erstellt wurde, nehmen wir eine weitere Änderung an der gesamten Zeichenfolge vor:
T`d`À-É
Dadurch werden die Ziffern durch die erweiterten ASCII-Buchstaben ersetzt
ÀÁÂÃÄÅÆÇÈÉ
Da sie sowohl im Sechseck als auch in der Zielzeichenfolge ersetzt werden, hat dies keinen Einfluss darauf, ob die Zeichenfolge übereinstimmt oder nicht. Außerdem, da es sich um Buchstaben handelt \w
und diese \b
immer noch als Sechseckzellen identifiziert werden. Der Vorteil dieser Ersetzung besteht darin, dass wir nun \D
in der kommenden regulären Ausdrucksweise jedes Zeichen (insbesondere Zeilenvorschübe sowie Nicht-Zeilenvorschübe) abgleichen können. Wir können die s
Option nicht verwenden, um dies zu erreichen, da wir .
Zeichen ohne Zeilenvorschub an mehreren Stellen abgleichen müssen.
Jetzt das letzte Bit: Bestimmen, ob ein Pfad mit unserer angegebenen Zeichenfolge übereinstimmt. Dies geschieht mit einem einzigen monströsen Regex. Sie könnten sich fragen, warum?!?! Nun, das ist im Grunde genommen ein Backtracking-Problem: Sie beginnen irgendwo und versuchen einen Pfad, solange er mit der Zeichenfolge übereinstimmt, und wenn dies nicht der Fall ist, gehen Sie zurück und versuchen Sie es mit einem anderen Nachbarn als dem zuletzt funktionierenden Zeichen. Die eine SacheDas, was Sie bei der Arbeit mit Regex kostenlos bekommen, ist Backtracking. Das ist buchstäblich das einzige, was die Regex-Engine macht. Wenn wir also nur einen Weg finden, um einen gültigen Pfad zu beschreiben (der für diese Art von Problem schwierig genug ist, aber mit Bilanzgruppen definitiv möglich ist), dann wird die Regex-Engine diesen Pfad unter allen möglichen für uns herausfinden. Es wäre sicherlich möglich, die Suche manuell in mehreren Schritten durchzuführen ( und das habe ich in der Vergangenheit getan ), aber ich bezweifle, dass es in diesem speziellen Fall kürzer wäre.
Ein Problem bei der Implementierung mit einem regulären Ausdruck ist, dass wir den Cursor des regulären Ausdrucksmoduls während des Zurückverfolgens nicht willkürlich durch den String bewegen können (was wir benötigen würden, da der Pfad möglicherweise nach oben oder unten führt). Stattdessen verfolgen wir unseren eigenen "Cursor" in einer Erfassungsgruppe und aktualisieren diesen bei jedem Schritt (wir können mit einem Lookaround vorübergehend an die Position des Cursors springen). Auf diese Weise können wir auch alle früheren Positionen speichern, anhand derer wir überprüfen, ob wir die aktuelle Position noch nicht besucht haben.
Also lasst uns loslegen. Hier ist eine etwas vernünftigere Version des regulären Ausdrucks mit benannten Gruppen, Einrückung, weniger zufälliger Reihenfolge der Nachbarn und einigen Kommentaren:
\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
Ich hoffe, dass die allgemeine Idee hieraus ungefähr klar wird. Schauen wir uns als Beispiel für eine dieser Bewegungen entlang des Pfades das Bit an, das den Cursor nach Süden bewegt:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Denken Sie daran, dass Lookbehinds von rechts nach links (oder von unten nach oben) gelesen werden sollten, da dies die Reihenfolge ist, in der sie ausgeführt werden:
(?<=
(?<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).
)
Beachten Sie, dass es nicht erforderlich ist, einen Anker vor den \k<pos>
zu setzen, um sicherzustellen, dass dieser tatsächlich den Anfang der Zeichenfolge erreicht. <pos>
Beginnt immer mit einer Menge ×
, die nirgendwo anders zu finden ist, und fungiert daher bereits als impliziter Anker.
Ich möchte diesen Beitrag nicht mehr als nötig aufblähen, deshalb gehe ich nicht auf die anderen 11 Fälle im Detail ein, aber im Prinzip funktionieren sie alle ähnlich. Wir überprüfen <next>
mit Hilfe von Bilanzkreisen, ob von der alten Cursorposition aus eine bestimmte (zulässige) Richtung gefunden werden kann, und speichern dann die Zeichenfolge bis zu dieser Übereinstimmung als neue Cursorposition in <pos>
.