Przepraszam, tym razem brak heksagonii ...
Liczba bajtów zakłada kodowanie 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
Oczekuje ciągu docelowego w pierwszym wierszu i sześciokąta w drugim wierszu danych wejściowych. Drukuje 0
lub 1
odpowiednio.
Wypróbuj online! (Pierwszy wiersz włącza zestaw testowy, gdzie każdy wiersz jest przypadkiem testowym, używającym ¦
do separacji zamiast podawania linii).
Właściwym sposobem rozwiązania tego problemu jest oczywiście wyrażenie regularne. ;) A gdyby nie to, że wyzwanie to obejmuje również procedurę rozwijania sześciokąta , odpowiedź ta składałaby się tylko z jednego ~ 600-bajtowego wyrażenia regularnego.
Nie jest to jeszcze optymalnie zoptymalizowane, ale jestem bardzo zadowolony z wyniku (moja pierwsza działająca wersja, po usunięciu nazwanych grup i innych rzeczy wymaganych dla zdrowia psychicznego, miała około 1000 bajtów). Myślę, że mógłbym zaoszczędzić około 10 bajtów, zamieniając kolejność łańcucha i sześciokąta, ale wymagałoby to całkowitego przepisania wyrażenia regularnego na końcu, czego nie czuję teraz. Istnieje również 2-bajtowa oszczędność poprzez pominięcie G
sceny, ale znacznie spowalnia to rozwiązanie, więc poczekam z wprowadzeniem tej zmiany, dopóki nie upewnię się, że grałem w nią najlepiej, jak potrafię.
Wyjaśnienie
Główna część tego rozwiązania w szerokim zakresie wykorzystuje grupy równoważące , więc polecam je przeczytać, jeśli chcesz zrozumieć, jak to działa w szczegółach (nie obwiniam cię, jeśli nie ...).
Pierwsza część rozwiązania (tj. Wszystko oprócz dwóch ostatnich wierszy) to zmodyfikowana wersja mojej odpowiedzi na Unfolding the Hexagony code source . Konstruuje sześciokąt, pozostawiając nietknięty ciąg docelowy (i faktycznie tworzy sześciokąt przed ciągiem docelowym). Wprowadziłem pewne zmiany w poprzednim kodzie, aby zapisać bajty:
- Znak tła jest
×
zamiast spacji, aby nie powodował konfliktu z potencjalnymi spacjami na wejściu.
_
Zamiast tego występuje znak „brak operacji / symbol wieloznaczny” .
, dzięki czemu komórki siatki mogą być niezawodnie identyfikowane jako znaki słowne.
- Po wstawieniu sześciokąta nie wstawiam żadnych spacji ani wcięć. Daje mi to nachylony sześciokąt, ale w rzeczywistości jest o wiele wygodniejszy w pracy, a zasady sąsiedztwa są dość proste.
Oto przykład. Dla następującego przypadku testowego:
ja
abcdefghij
Otrzymujemy:
××abc
×defg
hij__
____×
___××
ja
Porównaj to ze zwykłym układem sześciokąta:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Widzimy, że sąsiedzi są teraz zwykłymi 8 sąsiadami Moore, z wyjątkiem północno-zachodnich i południowo-wschodnich sąsiadów. Musimy więc sprawdzić przyleganie poziome, pionowe i południowo-zachodnie / północno-wschodnie (dobrze, a potem są krawędzie owijania). Korzystanie z tego bardziej zwartego układu ma również tę zaletę, że będziemy mogli wykorzystać je ××
na końcu, aby określić rozmiar sześciokąta w locie, gdy go potrzebujemy.
Po zbudowaniu tego formularza wprowadzamy jeszcze jedną zmianę do całego łańcucha:
T`d`À-É
Zastępuje to cyfry rozszerzonymi literami ASCII
ÀÁÂÃÄÅÆÇÈÉ
Ponieważ są one zastępowane zarówno w sześciokącie, jak i w ciągu docelowym, nie wpłynie to na to, czy ciąg zostanie dopasowany, czy nie. Ponadto, ponieważ są to litery \w
i \b
nadal identyfikują je jako komórki sześciokąta. Zaletą wykonania tej zamiany jest to, że możemy teraz użyć \D
w nadchodzącym wyrażeniu regularnym, aby dopasować dowolny znak (w szczególności znaki linefeed, a także znaki non-linefeed). Nie możemy skorzystać z s
opcji, aby to osiągnąć, ponieważ .
w wielu miejscach będziemy musieli dopasować znaki nieciągłe.
Teraz ostatni bit: ustalenie, czy jakakolwiek ścieżka pasuje do naszego podanego ciągu. Odbywa się to za pomocą jednego monstrualnego wyrażenia regularnego. Możesz zadać sobie pytanie, dlaczego?!?! Cóż, jest to zasadniczo problem z cofaniem się: zaczynasz gdzieś i próbujesz ścieżki, dopóki pasuje ona do łańcucha, a kiedy już nie, wycofujesz się i próbujesz innego sąsiada od ostatniej działającej postaci. Jednoktóre otrzymujesz za darmo podczas pracy z regexem jest cofanie się. To dosłownie jedyna rzecz, którą robi silnik regex. Jeśli więc znajdziemy sposób na opisanie prawidłowej ścieżki (co jest dość skomplikowane dla tego rodzaju problemu, ale z pewnością możliwe w przypadku grup równoważących), wówczas silnik wyrażenia regularnego wyszuka znalezienie tej ścieżki wśród wszystkich możliwych dla nas. Z pewnością byłoby możliwe ręczne zaimplementowanie wyszukiwania na wielu etapach ( a robiłem to w przeszłości ), ale wątpię, aby w tym konkretnym przypadku było ono krótsze.
Jednym z problemów związanych z implementacją tego z wyrażeniem regularnym jest to, że nie możemy arbitralnie przecinać kursora silnika wyrażenia regularnego w przód iw tył przez łańcuch podczas cofania (co byłoby nam potrzebne, ponieważ ścieżka może iść w górę lub w dół). Zamiast tego śledzimy własnego „kursora” w grupie przechwytywania i aktualizujemy go na każdym kroku (możemy tymczasowo przejść do pozycji kursora za pomocą obejrzenia). Dzięki temu możemy również przechowywać wszystkie poprzednie pozycje, których użyjemy, aby sprawdzić, czy nie odwiedziliśmy poprzedniej pozycji.
Przejdźmy więc do tego. Oto nieco rozsądniejsza wersja wyrażenia regularnego, z nazwanymi grupami, wcięciami, mniej losową kolejnością sąsiadów i kilkoma komentarzami:
\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
Mam nadzieję, że ogólna idea jest w przybliżeniu jasna z tego. Jako przykład działania jednego z tych ruchów wzdłuż ścieżki, spójrzmy na bit, który przesuwa kursor na południe:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Pamiętaj, że spojrzenia powinny być odczytywane od prawej do lewej (lub od dołu do góry), ponieważ w takiej kolejności są wykonywane:
(?<=
(?<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).
)
Zauważ, że nie jest konieczne umieszczanie kotwicy przed, \k<pos>
aby upewnić się, że faktycznie osiągnie początek łańcucha. <pos>
zawsze zaczyna się od ilości ×
, której nie można znaleźć nigdzie indziej, więc działa to już jako ukryta kotwica.
Nie chcę przesadzać z tym postem bardziej niż to konieczne, więc nie będę szczegółowo omawiał pozostałych 11 przypadków, ale w zasadzie wszystkie działają podobnie. Sprawdzamy, <next>
czy można znaleźć w jakimś określonym (dopuszczalnym) kierunku od starej pozycji kursora za pomocą grup równoważących, a następnie przechowujemy ciąg znaków do tego dopasowania jako nową pozycję kursora w <pos>
.