Maaf teman, tidak ada Hexagony saat ini ...
Hitungan byte mengasumsikan penyandian 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
Mengharapkan string target pada baris pertama dan segi enam pada baris kedua input. Cetakan 0
atau 1
sesuai.
Cobalah online! (Baris pertama memungkinkan suite uji, di mana setiap baris adalah kasus uji, menggunakan ¦
untuk pemisahan dan bukan linefeed.)
Cara yang tepat untuk mengatasi tantangan ini adalah dengan regex tentunya. ;) Dan jika bukan karena fakta bahwa tantangan ini juga melibatkan prosedur membuka segi enam , jawaban ini sebenarnya hanya terdiri dari satu regex panjang ~ 600 byte.
Ini belum cukup optimal bermain golf, tapi saya cukup senang dengan hasilnya (versi kerja pertama saya, setelah menghapus grup bernama dan hal-hal lain yang diperlukan untuk kewarasan, sekitar 1000 byte). Saya pikir saya bisa menghemat sekitar 10 byte dengan menukar urutan string dan hexagon tetapi akan membutuhkan penulisan ulang lengkap regex pada akhirnya, yang saya tidak rasakan saat ini. Ada juga penghematan 2-byte dengan menghilangkan G
panggung, tetapi itu sangat memperlambat solusinya, jadi saya akan menunggu dengan membuat perubahan itu sampai saya yakin saya telah bermain golf ini sebaik yang saya bisa.
Penjelasan
Bagian utama dari solusi ini membuat penggunaan ekstensif kelompok penyeimbang , jadi saya sarankan membaca mereka, jika Anda ingin memahami bagaimana ini bekerja secara rinci (saya tidak akan menyalahkan Anda jika Anda tidak ...).
Bagian pertama dari solusi (yaitu semuanya kecuali dua baris terakhir) adalah versi modifikasi dari jawaban saya untuk Unfolding the Hexagony source code . Itu membangun hexagon, sambil membiarkan string target tidak tersentuh (dan itu sebenarnya membangun hexagon sebelum string target). Saya telah membuat beberapa perubahan pada kode sebelumnya untuk menghemat byte:
- Karakter latar belakang
×
bukannya spasi sehingga tidak bertentangan dengan ruang potensial dalam input.
- No-op / wildcard karakter
_
bukan .
, sehingga sel-sel jaringan dapat diidentifikasi andal sebagai karakter kata.
- Saya tidak memasukkan spasi atau lekukan setelah segi enam pertama kali dibangun. Itu memberi saya segi enam miring, tapi itu sebenarnya jauh lebih nyaman untuk bekerja dengan dan aturan kedekatan cukup sederhana.
Berikut ini sebuah contoh. Untuk test case berikut:
ja
abcdefghij
Kita mendapatkan:
××abc
×defg
hij__
____×
___××
ja
Bandingkan ini dengan tata letak hexagon yang biasa:
a b c
d e f g
h i j _ _
_ _ _ _
_ _ _
Kita bisa melihat bahwa para tetangga sekarang semua tetangga Moore-8 yang biasa, kecuali tetangga barat laut dan tenggara. Jadi kita perlu memeriksa adjacency horisontal, vertikal dan barat daya / timur laut (baik dan kemudian ada tepi pembungkus). Menggunakan tata letak yang lebih ringkas ini juga memiliki bonus bahwa kita akan dapat menggunakannya ××
pada akhirnya untuk menentukan ukuran segi enam saat kita membutuhkannya.
Setelah formulir ini dibuat, kami membuat satu lagi perubahan pada seluruh string:
T`d`À-É
Ini menggantikan digit dengan huruf ASCII yang diperluas
ÀÁÂÃÄÅÆÇÈÉ
Karena mereka diganti baik di hexagon dan di string target, ini tidak akan mempengaruhi apakah string cocok atau tidak. Juga, karena mereka adalah huruf \w
dan \b
masih mengidentifikasi mereka sebagai sel segi enam. Manfaat melakukan substitusi ini adalah bahwa kita sekarang dapat menggunakan \D
dalam regex yang akan datang untuk mencocokkan karakter apa pun (khususnya, umpan baris serta karakter non-umpan baris). Kami tidak dapat menggunakan s
opsi untuk mencapai itu, karena kami harus .
mencocokkan karakter non-linefeed di beberapa tempat.
Sekarang bit terakhir: menentukan apakah ada jalur yang cocok dengan string kami. Ini dilakukan dengan regex raksasa tunggal. Anda mungkin bertanya pada diri sendiri mengapa?!?! Nah, ini pada dasarnya masalah penelusuran mundur: Anda memulai suatu tempat dan mencoba jalur selama cocok dengan string, dan sekali tidak Anda mundur dan mencoba tetangga yang berbeda dari karakter terakhir yang bekerja. Satu halyang Anda dapatkan secara gratis saat bekerja dengan regex mundur. Itulah satu-satunya hal yang dilakukan mesin regex. Jadi jika kita hanya menemukan cara untuk menggambarkan jalur yang valid (yang cukup rumit untuk masalah seperti ini, tapi pasti mungkin dengan kelompok penyeimbang), maka mesin regex akan memilah menemukan jalan itu di antara semua yang mungkin bagi kita. Tentu akan mungkin untuk mengimplementasikan pencarian secara manual dengan beberapa tahap ( dan saya telah melakukannya di masa lalu ), tapi saya ragu itu akan lebih pendek dalam kasus khusus ini.
Salah satu masalah dengan menerapkan ini dengan regex adalah bahwa kita tidak dapat secara sewenang-wenang mengayunkan kursor mesin regex bolak-balik melalui string selama backtracking (yang kita perlukan karena jalur mungkin naik atau turun). Jadi alih-alih, kami melacak "kursor" kami sendiri dalam grup penangkap dan memutakhirkannya di setiap langkah (kami dapat pindah ke posisi kursor untuk sementara dengan pencarian). Ini juga memungkinkan kita untuk menyimpan semua posisi sebelumnya yang akan kita gunakan untuk memeriksa bahwa kita belum mengunjungi posisi saat ini sebelumnya.
Jadi mari kita mulai. Berikut ini adalah versi regex yang sedikit lebih waras, dengan grup yang diberi nama, indentasi, urutan tetangga yang kurang acak, dan beberapa komentar:
\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
Saya berharap bahwa ide umum kurang lebih jelas dari ini. Sebagai contoh bagaimana salah satu gerakan di sepanjang jalan itu bekerja, mari kita lihat bit yang menggerakkan kursor ke selatan:
(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)
Ingatlah bahwa lookbehinds harus dibaca dari kanan ke kiri (atau dari bawah ke atas), karena itulah urutan pelaksanaannya:
(?<=
(?<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).
)
Perhatikan bahwa tidak perlu meletakkan jangkar di depan \k<pos>
untuk memastikan bahwa ini benar-benar mencapai awal string. <pos>
selalu dimulai dengan jumlah ×
yang tidak dapat ditemukan di tempat lain, jadi ini sudah bertindak sebagai jangkar implisit.
Saya tidak ingin menggembungkan posting ini lebih dari yang diperlukan, jadi saya tidak akan membahas 11 kasus lainnya secara terperinci, tetapi pada prinsipnya semuanya bekerja sama. Kami memeriksa bahwa <next>
dapat ditemukan dalam beberapa arah tertentu (dapat diterima) dari posisi kursor lama dengan bantuan kelompok penyeimbang, dan kemudian kami menyimpan string hingga cocok dengan posisi kursor baru di <pos>
.