HexaRegex: A Tribute to Martin Ender

37

4

Martin Ender recently hit 100K, and has come up with some pretty awesome languages. We're going to have a bit of fun with one of them, Hexagony (and a bit of regex for Retina)

As a brief overview, you need to write a program that inputs a Hexagony grid and determines if there is a path on that grid that matches a string of text

Generating

Hexagony generates hexagons from a string of text using the following steps:

  1. Calculate the minimum hexagon size (take the length of the string and round up to the nearest hex number)
  2. Wrapping the text into a hexagon of the above size
  3. Filling the remaining locations with ..

For example, the string of text abcdefghijklm requires a hexagon of side-length 3, and therefore becomes:

   a b c
  d e f g
 h i j k l
  m . . .
   . . .

Now, notice that there are 6 possible directions you can travel in a hexagon. For example, in the above hexagon, e is adjacent to abfjid.

Wrapping

Furthermore, in Hexagony, hexagons wrap:

   . . . .          . a . .          . . f .          . a . .   
  a b c d e        . . b . .        . . g . .        . b . . f  
 . . . . . .      g . . c . .      . . h . . a      . c . . g . 
. . . . . . .    . h . . d . .    . . u . . b .    . d . . h . .
 f g h i j k      . i . . e .      . j . . c .      e . . i . . 
  . . . . .        . j . . f        k . . d .        . . j . .  
   . . . .          . k . .          . . e .          . k . .   

If you look at the 2nd and 4th example, notice how a and k are in the same spots, despite the fact that you are wrapping in different directions. Due to this fact, these spots are only adjacent to 5 other locations.

To make this clearer:

   a b c d
  e f g h i
 j k l m n o
p q r s t u v
 w x y z A B
  C D E F G
   H I J K
  1. Edges wrap to their opposite neighbor (b->I and G->j).
  2. Top/bottom corners wrap to the opposite center corner and up/down (d->K,p and H->a,v).
  3. Center corners wrap to both the top and bottom corners (v->a,H)

Paths

A path to be a sequence of adjacent locations without returning to the same location.

   a b c
  d e f g
 h i f k l
  m . . .
   . . .

In the above hexagon, aefkgm is a valid path. However, abfd is not a valid path (f and d aren't adjacent), and abea isn't valid (returns to the a location).

We can use these paths to match text (like regex). An alpha-numeric character matches itself (and only itself), and a . matches any character. For example, the path aej..lgm would match aej..lgm, aejAAlgm, aeja.lgm, or aej^%gm.

Input/Output

Your program should take two strings (in any order). The first string will be nonempty, and consist of only alphanumeric characters [a-zA-Z0-9]. This will represent the hexagon you are operating on. The second string will consist of printable characters.

You need to return a truthy value iff there is a path in the hexagon that matches the string of text given, otherwise a falsy value.

Test cases

Truthy:

"a","a"
"ab","a"
"ab","b"
"ab","ba"
"ab","aba"
"ab","&"
"ab","#7.J!"
"ab","aaaaaa"
"ab","bgjneta"
"ab","cebtmaa"
"abcdefg","dfabcg"
"AbCDeFG","GCbAeFD"
"aaaabbb","aaababb"
"abcdefghijklmnopqrs","alq"
"abcdefghijklmnopqrs","aqnmiedh"
"abcdefghijklmnopqrs","adhcgkorbefjimnqlps"
"11122233344455","12341345123245"
"abcdefgh","h%a"
"abcdefghijklm","a)(@#.*b"
"abcdefghijklm","a)(@#.*i"
"abcdefghij","ja"
"abcdefghijklmno","kgfeia"
"abcdefghijklmno","mmmmmiea"
"abcdefghijklmno","mmmmmlae"
"abcdefghijklmno","ja"
"abcdefghijklmnopqrs","eijfbadhmnokgcsrql"

Falsy:

"a","b"
"a","%"
"a","."
"a","aa"
"a","a."
"ab","#7.J!*"
"ab","aaaaaaa"
"ab","aaaabaaa"
"ab","123456"
"abcdefg","bfgedac"
"abcdefg","gecafdb"
"abcdefg","GCbaeFD"
"aaaabbb","aaaaabb"
"abcdefghijklmnopqrs","aqrcgf"
"abcdefghijklmnopqrs","adhlcgknbeifjm"
"abcdefghijklmnopqrs","ja"
"abcdefghijklm","a)(@#.*&"
"abcdefghijklmno","a)(@bfeijk"
"abcdefghijklmno","kgfeic"
"abcdefghijklmno","mmmmmmiea"

This is a , so make your answers as short as possible in your favorite language.

Nathan Merrill

Posted 2016-08-02T23:38:15.623

Reputation: 13 591

21Somebody should do this in Hexagony. :D – James – 2016-08-02T23:52:26.467

hexAGONY xD have fun – Rohan Jhunjhunwala – 2016-08-02T23:57:34.413

@DrGreenEggsandIronMan I was going to suggest Retina also, but that's probably pretty trivial. :P – Rɪᴋᴇʀ – 2016-08-03T00:01:33.393

2

Related: http://codegolf.stackexchange.com/q/66708/29750

– NinjaBearMonkey – 2016-08-03T00:05:29.907

9I was initially very confused by the truthy examples until I realized that the hexagon is the source of the regex(es), so to speak, not the second string. Which is still mind-bending... :P – El'endia Starman – 2016-08-03T01:40:00.700

Apparently, I made this challenge too difficult, as I haven't gotten any answers yet (and to think that I was considering allowing any regex symbol in the hexagon) – Nathan Merrill – 2016-08-03T12:41:58.797

5@DrGreenEggsandIronMan I'll offer a 500-rep bounty if someone does do this in Hexagony. – AdmBorkBork – 2016-08-03T13:21:55.397

('abcdefghijklm', 'h%a'): h only connects to the bottom right corner, which doesn't connect to a. ('abcdefghijklmno', 'ja') : j is completely entrapped. – Nathan Merrill – 2016-08-29T15:29:04.207

As for kgfeia, I already had it in the truthy, but forgot to change it. You were correct about side-to-side wrapping. When I initially wrote the spec, I thought they wrapped. – Nathan Merrill – 2016-08-29T15:36:44.400

You don't need to use h or j, there are .s at the bottom you can get to a from – Blue – 2016-08-29T15:38:57.757

oooh, I didn't think about that. Good catch. – Nathan Merrill – 2016-08-29T15:40:05.193

I guess you should also change your example to a hexagon that's completely filled in as well (it has dots that make the path valid) – Blue – 2016-08-29T16:11:48.447

2@Blue An example of an unfilled hexagon is important. More importantly, I've made the distinction between a "path" and a "regex". – Nathan Merrill – 2016-08-29T16:13:42.200

Answers

14

Retina, 744 bytes

Sorry folks, no Hexagony this time...

Byte count assumes ISO 8859-1 encoding.

.+¶
$.'$*_¶$&
^_¶
¶
((^_|\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

Expects the target string on the first line and the hexagon on the second line of the input. Prints 0 or 1 accordingly.

Try it online! (The first line enables a test suite, where each line is a test case, using ¦ for separation instead of a linefeed.)

The proper way to solve this challenge is with a regex of course. ;) And if it wasn't for the fact that this challenge also involves the unfolding procedure of the hexagon, this answer would actually consist of nothing but a single ~600-byte long regex.

This isn't quite optimally golfed yet, but I'm quite happy with the result (my first working version, after removing named groups and other stuff required for sanity, was around 1000 bytes). I think I could save about 10 bytes by swapping the order of the string and the hexagon but it would require a complete rewrite of the regex at the end, which I'm not feeling up to right now. There's also a 2-byte saving by omitting the G stage, but it slows down the solution considerably, so I'll wait with making that change until I'm sure I've golfed this as well as I can.

Explanation

The main part of this solution makes extensive use of balancing groups, so I recommend reading up on them, if you want to understand how this works in detail (I won't blame you if you don't...).

The first part of the solution (i.e. everything except the last two lines) is a modified version of my answer to Unfolding the Hexagony source code. It constructs the hexagon, while leaving the target string untouched (and it actually constructs the hexagon before the target string). I've made some changes to the previous code to save bytes:

  • The background character is × instead of a space so that it doesn't conflict with potential spaces in the input.
  • The no-op/wildcard character is _ instead ., so that grid cells can be reliably identified as word characters.
  • I don't insert any spaces or indentation after the hexagon is first constructed. That gives me a slanted hexagon, but that is actually much more convenient to work with and the adjacency rules are fairly simple.

Here is an example. For the following test case:

ja
abcdefghij

We get:

××abc
×defg
hij__
____×
___××
ja

Compare this with the usual layout of the hexagon:

  a b c
 d e f g
h i j _ _
 _ _ _ _
  _ _ _

We can see that the neighbours are now all of the usual 8 Moore-neighbours, except the north-west and south-east neighbours. So we need to check horizontal, vertical and south-west/north-east adjacency (well and then there are the wrapping edges). Using this more compact layout also has the bonus that we'll be able to use those ×× at the end to determine the size of the hexagon on the fly when we need it.

After this form has been constructed, we make one more change to the entire string:

T`d`À-É

This replaces the digits with the extended ASCII letters

ÀÁÂÃÄÅÆÇÈÉ

Since they are replaced both in the hexagon and in the target string this won't affect whether the string is matched or not. Also, since they are letters \w and \b still identify them as hexagon cells. The benefit of doing this substitution is that we can now use \D in the upcoming regex to match any character (specifically, linefeeds as well as non-linefeed characters). We can't use the s option to accomplish that, because we'll need . to match non-linefeed characters in several places.

Now the last bit: determining whether any path matches our given string. This is done with a single monstrous regex. You might ask yourself why?!?! Well, this is fundamentally a backtracking problem: you start somewhere and attempt a path as long as it matches the string, and once it doesn't you backtrack and try a different neighbour from the last character that worked. The one thing that you get for free when working with regex is backtracking. That's literally the only thing the regex engine does. So if we just find a way to describe a valid path (which is tricky enough for this kind of problem, but definitely possible with balancing groups), then the regex engine will sort out finding that path among all possible ones for us. It would certainly be possible to implement the search manually with multiple stages (and I've done so in the past), but I doubt it would be shorter in this particular case.

One issue with implementing this with a regex is that we can't arbitrarily weave the regex engine's cursor back and forth through the string during backtracking (which we'd need since the path might go up or down). So instead, we keep track of our own "cursor" in a capturing group and update that at every step (we can move to the position of the cursor temporarily with a lookaround). This also enables us to store all past positions which we'll use to check that we haven't visited the current position before.

So let's get to it. Here is a slightly saner version of the regex, with named groups, indentation, less random order of neighbours and some comments:

\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

I hope that the general idea is roughly clear from this. As an example for how one of those movements along the path works, let's look at the bit that moves the cursor south:

(?<=(?<pos>\k<pos>.(?>(?<-x>.)*)¶.*)\k<next>(?<x>.)*¶\D*)

Remember that lookbehinds should be read from right to left (or bottom to top), because that's the order they are executed in:

(?<=
  (?<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).
)

Note that it's not necessary to put an anchor in front of the \k<pos> to ensure that this actually reaches the beginning of the string. <pos> always starts with an amount of × that can't be found anywhere else, so this acts as an implicit anchor already.

I don't want to bloat this post more than necessary, so I won't go into the other 11 cases in detail, but in principle they all work similarly. We check that <next> can be found in some specific (admissible) direction from the old cursor position with the help of balancing groups, and then we store the string up to that match as the new cursor position in <pos>.

Martin Ender

Posted 2016-08-02T23:38:15.623

Reputation: 184 808

13

Python 3, 990 943 770 709 bytes

First answer, yay!

EDIT: Golfed adjacency list making. I now use a slightly different formula

EDIT 2: Removed unnecessary fluff, golfed a lot more.

EDIT 3: Shortened the code for conversion from index in list to coordinates, golfed a few more things.

The majority of the bytes relates to making the adjacency list (it has the most potential to be golfed). From then, it's a simple matter of brute-forcing the solution (which I might be able to do in less bytes).

Golfed:

from math import*
b=abs
c=max
e=range
f=len
A=input()
B=input()
C=ceil(sqrt((f(A)-.25)/3)+.5)
D=3*C*~-C+1
E=2*C-1
F=C-1
A+='.'*(D-f(A))
G=[set()for x in e(D)]
I=lambda H:sum(E+.5-b(t-F+.5)for t in e(int(H+F)))
for x in e(D):
 r=sum([[J-F]*(E-b(J-F))for J in e(E)],[])[x];q=x-I(r);s=-q-r;a=lambda q,r:G[x].add(int(q+I(r)));m=c(map(b,[q,r,s]))
 if m==F:
  if q in(m,-m):a(-q,-s)
  if r in(m,-m):a(-s,-r)
  if s in(m,-m):a(-r,-q)
 for K,L in zip([1,0,-1,-1,0,1],[0,1,1,0,-1,-1]):
  M,H=q+K,r+L
  if c(map(b,[M,H,-M-H]))<C:a(M,H)
def N(i,O,P):
 Q=O and O[0]==A[i]or'.'==A[i];R=0
 if(2>f(O))*Q:R=1
 elif Q:R=c([(x not in P)*N(x,O[1:],P+[i])for x in G[i]]+[0])
 return R
print(c([N(x,B,[])for x in e(D)])*(f(B)<=D))

Ungolfed w/ explanation:

from math import*

#Rundown of the formula:
# * Get data about the size of the hexagon
# * Create lookup tables for index <-> coordinate conversion
#   * q=0, r=0 is the center of the hexagon
#   * I chose to measure in a mix of cubic and axial coordinates,
#     as that allows for easy oob checks and easy retrevial  
# * Create the adjacency list using the lookup tables, while
#   checking for wrapping
# * Brute-force check if a path in the hexagon matches the
#   expression

# shorten functions used a lot
b=abs
c=max
e=range

# Get input

prog=input()
expr=input()

# sdln = Side length
# hxln = Closest hexagonal number
# nmrw = Number of rows in the hexagon
# usdl = one less than the side length. I use it a lot later

sdln=ceil(sqrt((len(prog)-.25)/3)+.5)
hxln=3*sdln*~-sdln+1
nmrw=2*sdln-1
usdl=sdln-1

# Pad prog with dots

prog+='.'*(hxln-len(prog))

# nmbf = Number of elements before in each row
# in2q = index to collum
# in2r = index to row

nmbf=[0]*nmrw
in2q=[0]*hxln
in2r=[0]*hxln

#  4    5
#   \  /
# 3 -- -- 0
#   /  \ 
#  2    1

# dirs contains the q,r and s values needed to move a point
# in the direction refrenced by the index

qdir=[1,0,-1,-1,0,1]
rdir=[0,1,1,0,-1,-1]

# generate nmbf using a summation formula I made

for r in e(nmrw-1):
    nmbf[r+1]=int(nmbf[r]+nmrw+.5-b(r-sdln+1.5))

# generate in2q and in2r using more formulas
# cntr = running counter

cntr=0
for r in e(nmrw):
    bgnq=c(-r,1-sdln)
    for q in e(nmrw-b(r-sdln+1)):
        in2q[cntr]=bgnq+q
        in2r[cntr]=r-usdl
        cntr+=1

# adjn = Adjacency sets

adjn=[set()for x in e(hxln)]

# Generate adjacency sets

for x in e(hxln):
    #Get the q,r,s coords
    q,r=in2q[x],in2r[x]
    s=-q-r
    # a = function to add q,r to the adjacency list
    a=lambda q,r:adjn[x].add(q+nmbf[r+usdl])
    # m = absolute value distance away from the center
    m=c(map(b,[q,r,s]))
    # if we are on the edge (includes corners)...
    if m==usdl:
        # add the only other point it wraps to
        if q in(m,-m):
            a(-q,-s)
        if r in(m,-m):
            a(-s,-r)
        if s in(m,-m):
            a(-r,-q)
    # for all the directions...
    for d in e(6):
        # tmp{q,r,s} = moving in direction d from q,r,s
        tmpq,tmpr=q+qdir[d],r+rdir[d]
        # if the point we moved to is in bounds...
        if c(map(b,[tmpq,tmpr,-tmpq-tmpr]))<sdln:
            # add it
            a(tmpq,tmpr)

# Recursive path checking function
def mtch(i,mtst,past):
    # dmch = Does the place we are on in the hexagon match
    #        the place we are in the expression?
    # out = the value to return
    dmch=mtst and mtst[0]==prog[i]or'.'==prog[i]
    out=0
    # if we are at the end, and it matches...
    if(2>len(mtst))*dmch:
        out=1
    # otherwise...
    elif dmch:
        # Recur in all directions that we haven't visited yet
        # replace '*' with 'and' to speed up the recursion
        out=c([(x not in past)*mtch(x,mtst[1:],past+[i])for x in adjn[i]]+[0])
    return out

# Start function at all the locations in the hexagon
# Automatically return false if the expression is longer
# than the entire hexagon
print(c([mtch(x,expr,[])for x in e(hxln)])*(len(expr)<=hxln))

So close to Retina! :( Yay, beat Retina!

Blue

Posted 2016-08-02T23:38:15.623

Reputation: 1 986

5

Javascript (ES6), 511 500 496 bytes

(H,N)=>{C=(x,y)=>(c[x]=c[x]||[])[y]=y;S=d=>(C(x,y=x+d),C(y,x),C(s-x,s-y),C(s-y,s-x));r=(x,p,v)=>{p<N.length?(v[x]=1,c[x].map(n=>!v[n]&&(H[n]==N[p]||H[n]=='.')&&r(n,p+1,v.slice()))):K=1};for(e=x=K=0;(s=3*e*++e)<(l=H.length)-1;);H+='.'.repeat(s+1-l);for(a=[],b=[],c=[[]],w=e;w<e*2;){a[w-e]=x;b[e*2-w-1]=s-x;for(p=w;p--;x++){w-e||S(s-e+1);w<e*2-1&&(S(w),S(w+1));p&&S(1)}a[w]=x-1;b[e*3-++w]=s-x+1}a.map((v,i)=>S(b[i]-(x=v)));[N[0],'.'].map(y=>{for(x=-1;(x=H.indexOf(y,x+1))>-1;r(x,1,[]));});return K}

Ungolfed and commented

// Entry point
//   H = haystack (the string the hexagon is filled with)
//   N = needle (the substring we're looking for)
(H, N) => {
  // C(x, y) - Helper function to save a connection between two locations.
  //   x = source location
  //   y = target location
  C = (x, y) => (c[x] = c[x] || [])[y] = y;

  // S(d) - Helper function to save reciprocal connections between two locations
  //        and their symmetric counterparts.
  //   d = distance between source location (x) and target location
  S = d => (C(x, y = x + d), C(y, x), C(s - x, s - y), C(s - y, s - x));

  // r(x, p, v) - Recursive path search.
  //   x = current location in hexagon
  //   p = current position in needle
  //   v = array of visited locations
  r = (x, p, v) => {
    p < N.length ?
      (v[x] = 1, c[x].map(n => !v[n] && (H[n] == N[p] || H[n] == '.') &&
      r(n, p + 1, v.slice())))
    :
      K = 1
  };

  // Compute e = the minimum required edge width of the hexagon to store the haystack.
  // Also initialize:
  //   x = current location in hexagon
  //   l = length of haystack
  //   s = size of hexagon (number of locations - 1)
  //   K = fail/success flag
  for(e = x = K = 0; (s = 3 * e * ++e) < (l = H.length) - 1;);

  // Pad haystack with '.'
  H += '.'.repeat(s + 1 - l);

  // Build connections c[] between locations, using:
  //   x = current location
  //   w = width of current row
  //   p = position in current row
  // Also initialize:
  //   a[] = list of locations on top left and top right edges
  //   b[] = list of locations on bottom left and bottom right edges
  for(a = [], b = [], c = [[]], w = e; w < e * 2;) {
    a[w - e] = x;
    b[e * 2 - w - 1] = s - x;

    for(p = w; p--; x++) {
      // connection between top and bottom edges
      w - e || S(s - e + 1);
      // connections between current location and locations below it
      w < e * 2 - 1 && (S(w), S(w + 1));
      // connection between current location and next location
      p && S(1)
    }
    a[w] = x - 1;
    b[e * 3 - ++w] = s - x + 1
  }

  // Save connections between top left/right edges and bottom left/right edges.
  a.map((v, i) => S(b[i] - (x = v)));

  // Look for either the first character of the needle or a '.' in the haystack,
  // and use it as the starting point for the recursive search. All candidate
  // locations are tried out.
  [N[0], '.'].map(y => {
    for(x = -1; (x = H.indexOf(y, x + 1)) > -1; r(x, 1, []));
  });

  // Return fail/success flag.
  return K
}

Test cases

The snippet below will walk through all truthy and falsy test cases.

let f =
(H,N)=>{C=(x,y)=>(c[x]=c[x]||[])[y]=y;S=d=>(C(x,y=x+d),C(y,x),C(s-x,s-y),C(s-y,s-x));r=(x,p,v)=>{p<N.length?(v[x]=1,c[x].map(n=>!v[n]&&(H[n]==N[p]||H[n]=='.')&&r(n,p+1,v.slice()))):K=1};for(e=x=K=0;(s=3*e*++e)<(l=H.length)-1;);H+='.'.repeat(s+1-l);for(a=[],b=[],c=[[]],w=e;w<e*2;){a[w-e]=x;b[e*2-w-1]=s-x;for(p=w;p--;x++){w-e||S(s-e+1);w<e*2-1&&(S(w),S(w+1));p&&S(1)}a[w]=x-1;b[e*3-++w]=s-x+1}a.map((v,i)=>S(b[i]-(x=v)));[N[0],'.'].map(y=>{for(x=-1;(x=H.indexOf(y,x+1))>-1;r(x,1,[]));});return K}

let truthy = [
  ["a","a"],
  ["ab","a"],
  ["ab","b"],
  ["ab","ba"],
  ["ab","aba"],
  ["ab","&"],
  ["ab","#7.J!"],
  ["ab","aaaaaa"],
  ["ab","bgjneta"],
  ["ab","cebtmaa"],
  ["abcdefg","dfabcg"],
  ["AbCDeFG","GCbAeFD"],
  ["aaaabbb","aaababb"],
  ["abcdefghijklmnopqrs","alq"],
  ["abcdefghijklmnopqrs","aqnmiedh"],
  ["abcdefghijklmnopqrs","adhcgkorbefjimnqlps"],
  ["11122233344455","12341345123245"],
  ["abcdefgh","h%a"],
  ["abcdefghijklm","a)(@#.*b"],
  ["abcdefghijklm","a)(@#.*i"],
  ["abcdefghij","ja"],
  ["abcdefghijklmno","kgfeia"],
  ["abcdefghijklmno","mmmmmiea"],
  ["abcdefghijklmno","mmmmmlae"],
  ["abcdefghijklmno","ja"],
  ["abcdefghijklmnopqrs","eijfbadhmnokgcsrql"]
];

let falsy = [
  ["a","b"],
  ["a","%"],
  ["a","."],
  ["a","aa"],
  ["a","a."],
  ["ab","#7.J!*"],
  ["ab","aaaaaaa"],
  ["ab","aaaabaaa"],
  ["ab","123456"],
  ["abcdefg","bfgedac"],
  ["abcdefg","gecafdb"],
  ["abcdefg","GCbaeFD"],
  ["aaaabbb","aaaaabb"],
  ["abcdefghijklmnopqrs","aqrcgf"],
  ["abcdefghijklmnopqrs","adhlcgknbeifjm"],
  ["abcdefghijklmnopqrs","ja"],
  ["abcdefghijklm","a)(@#.*&"],
  ["abcdefghijklmno","a)(@bfeijk"],
  ["abcdefghijklmno","kgfeic"],
  ["abcdefghijklmno","mmmmmmiea"]
];

console.log(
  'Testing truthy test cases:',
  truthy.every(v => f(v[0], v[1])) ? 'passed' : 'failed'
);
console.log(
  'Testing falsy test cases:',
  falsy.every(v => !f(v[0], v[1])) ? 'passed' : 'failed'
);

Arnauld

Posted 2016-08-02T23:38:15.623

Reputation: 111 334