Pff, they kept pushing me to write an explanation. Ok. Here you go.

This challenge basically asked for backtracking, and perl already has
a backtracking system: the regex engine. So my idea was to take the
boggle board as a string to match on, and rewrite the target word as a
regex and then see if it matches.

The first problem is that on the board you sometimes have to go
backward, and regexes only go forward (I really wish perl would get
variable length lookbehind). So instead I'll take repeated copies
of the board as string to match on, and jump from board to board
during the match.

The second problem is the edges. if you write a board like

a b c d
e f g h
i j k l
m n o p

as the string "abcdefghijklmnop" (x repeater), you can see that e.g.
you can move one right from c to get to d as wanted, but you also can
move one right from d to e, which is NOT a valid boggle move. So i need
to introduce extra invalid characters in between the edges. Since I'm
going to repeat the board string, it also needs an edge at the bottom
or top, otherwise you can still wrap vertically. E.g. putting the extra
protection at the front the minimum string I can use is:

XXXXXabcdXefghXijklXmnop

where the X's must be characters that can't appear in a target word.
For the front string I decided to use $~ (STDOUT) which is a bit too
long, but that's no problem, we'll just skip a bit extra when moving
from board to board. So building the string becomes:

map$~.=$_%5&&pop,0..19

which gives:

STDOUT0ponm0lkji0hgfe0dcba

notice the string is backward, but that doesn't matter, since the boggle
rules are symmetric in all directions. The extra 0 after STDOUT is again
no poblem, just a bit of extra shifting. Also notice we can play with
that 19 to make it bigger. It will just cause extra 0's at the end, which
we can again compensate for when skipping from board to board. If we make
it big enough, there will even be enough extra 0's at the end that we
don't need to use $~, since these extra end 0's already protect against
edge skips. (Unfortunately I see no way to use that fact).

Next comes building the regex. Basically we want to make a sequence
AB into:
    A
    (skip to the next boards left up position relative to A)
    (choose to go 0, 1 or 2 steps forward)
    (choose to go 0, 1 or 2 steps downward)
    B
Where the () pieces can be in any order. For the given board string, that
would be:

    A.{19}.{0,2}(.{5}){0,2}B

So the first (naive) version of constructing the regex if the target
string (without \n) is in $_ would be:

    s/\B/.{19}.{0,2}(.{5}){0,2}/g

after which we can check if it can be made from the board is:

    ($~ x 16) =~ $_

We need at least as many copies of the board as there are letters in
the target string, which can have a size up to 16. I tried to use
the previous regex construction s/// as the multiplier in my final
solution, but it always turned out to be one too short, and adding 1
looses more strokes than just using a constant like 16. It would be
faster in the failure case though, since now the failing case tries
all possible starting positions).

The main problem left now is that the current method is wrong. It will
happily reuse positions already visited before, even staying put if
it likes to. Bummer.

But wait ! We can use @+ or @- to collect the positions where we matched
a letter. If we take these modulo boardstring length, we have absolute
positions on the board. So we are going to demand no repeating there.
We can use the (??{}) regex construct to conditionally construct a
regex piece that allows regex to progress beyond it or not.

The shortest way would likely be to after each matched letter in the
target word to check the current pos versus the old positions in @+ or
@-. Unfortunately I never got that working properly since there's a bug
in backtracking over (??{}) in that you get spurious undef entries which
take too much work too ignore them.

So instead i decided to only check at the total end, using this construct:

.*(??{grep$c{$_%26}++,[EMAIL PROTECTED]

For the moment assuming %c is empty, this will count how many times a
certain spot is used, and the grep will match if any spot is ever used
more than once. So you get 0 if every spot is used only once, 1 or more
otherwise. If we make sure there is a 0 somewhere after the last match
position, that will mean further match is possible and the whole regex
will match. And since 1-9 don't appear in the string, if a spot is
double used, it will not match, and the backtracking will resume. We can
get that 0 by adding an extra board or by increasing the 19 in the map
that constructs the board string.

However, %c is NOT empty. It's initialized with @+, so it actually has
all kinds of values already. We don't actually care about these unless
some keys are smaller than 26 however, since otherwise they don't
cause fake counts in $c{$_%26}++. If some positions ARE below 26, this
can however cause a spurious backtrack. But again this is no problem if
we attach an extra board. If there *is* a solution, then there will now
also be a solution starting at board 2, and in that case all values in
@+ (or @-) will be biger than 26. So everything works out.

The next problem is that now we have a runtime constructed regex 
containing (??{}), which is actually not allowed unless you do

use re "eval";

which is pretty long. Checking in the source of the re pragma, we see 
that what it reall does is effectively: 

BEGIN { $^H |= 0x00200000 }

That's good ! We wanted a BEGIN{} or INIT{} block anyways so that we
can use the perl -p/-n option to loop over STDIN, which needs us to
empty @ARGV first. So we want that board construction map inside a 
BEGIN/INIT anyways. INIT comes too late for $^H, so BEGIN it is.
And we need to set $^H to 2**21. mmm. Hey, that map returns at least 20, 
so we can use things like:

BEGIN{$^H=2<<map$~.=$_%5&&pop,0..19}

Still a pity we need that constant 2. So let's abuse the fact that
a << b is the same as a << (b % int_size) on modern CPU's:

perl -wle 'print 1 << 65'
2

Perl uses C to implement <<, and C normally maps it directly to some
hardware shift instruction. These nowadays ignore all bits beyond 
sizeof(int). Still, this depends on if all cpu's on which an official 
perl 5.8.0 exists behave like this. It's ok for every CPU I've been able
to test it on, but I don't know if it's always so (the main missing
CPU in my survey is the M68000 family. Anyone has such a perl ?)

Since $^H actually is initialized with 256 (2**8), if we shift left by
77, we get $^H = 2**21 as it should be (the loss of the 2**8 bit seems 
to cause no problems). That gives:

BEGIN{$<<=map$~.=$_%5&&pop,0..76}

The board string is now a lot longer, but that's ok, just properly 
increase all later constants.

All that's left now is someminor tricks:

  - We need to parenthesis so that positions are actually filled in in
    @+/@-. We already need a (.{5}){0,2} construct though. We can in 
    fact use these parenthesis, but we will only get the end of the last
    match in the repeat, so we actually should use @+ and not @-
  - s/\B//g only works properly if we have a -l option. But that means we
    can't handle the failing strings by printing "", we must then do a 
    conditional print. Too long. Instead let's set $_ to either the
    original string or "". But that means that /\B/ would also match 
    between the \n and end of string, so we get an extra spurious ()
    at the end. And if we just spiralled on the boggle board to the
    center of a 3x3 block, no position of that tail () is actually valid,
    so this is unacceptable. So instead go to s/./    $&  /g style 
    constructor. It means we get an extra \n in the result though. We
    can ignore that by using the x modifier to the regex.
    It also means we get some extra match stuff in front of the first 
    letter. But that's no problem. The position of that start doesn't 
    get into @+, and we had an extra board in front anyways which provides
    enough characters for the match.
  - At the end we need to print the original string in case of a match. 
    But we destroyed $_ ! Fortunately after the regex constructor, it's 
    in $` (that's actually a perl bug). We need to be careful though 
    since the final match will destroy it again. That's why you will see
    it quoted.
  - things like .{19} can be written as (.{5}){3}.{4}, and the multiples 
    of 5 and one can be absorbed in the (.{5}){0,2}.{0,2} we already have.

So in the end this gives this 114:

-p 
BEGIN{$<<=map$~.=$_%5&&pop,0..76}s/./.{1,3}(.{5}){6,8}$&/g;$_="$`"x($~x17)=~/$_.*(??{grep$c{$_%38}++,[EMAIL
 PROTECTED])/x

Reply via email to