I can't remember which monk asked for a word search puzzle program a couple of months ago. I did one in C++ 4 years ago for school, and now ported it to Perl. The solution is kinda wordy and could probably be cleaned up, but I thought I'd post it anyway. One observation was that coding in Perl was not as easy as I thought. Data structures created some pretty intense code and I was surprised that the C++ solution was cleaner in some ways.
#!/usr/bin/perl # wsp_r.pl Word search puzzle, Assignment 1 use strict; use warnings; # Constants use constant N => 0; use constant NE => 1; use constant E => 2; use constant SE => 3; use constant S => 4; use constant SW => 5; use constant W => 6; use constant NW => 7; use constant R => 0; use constant C => 1; use constant TRUE => 1; use constant FALSE => 0; ########################################################### ##################### main ############################ my @words; my @ltrs; read_file(\@ltrs, \@words); search(\@ltrs, \@words); writefile(\@ltrs, \@words); #################### end main ########################### ########################################################### sub step { my ($dir, $r, $c) = @_; if ($dir == N) {return (--$r, $c)} elsif ($dir == NE) {return (--$r, ++$c)} elsif ($dir == E) {return ($r, ++$c)} elsif ($dir == SE) {return (++$r, ++$c)} elsif ($dir == S) {return (++$r, $c)} elsif ($dir == SW) {return (++$r, --$c)} elsif ($dir == W) {return ($r, --$c)} elsif ($dir == NW) {return (--$r, --$c)} else { die "Inside 'step': invalid param\n" } } sub stepback { my ($dir, $r, $c) = @_; if ($dir == N) {return (++$r, $c)} elsif ($dir == NE) {return (++$r, --$c)} elsif ($dir == E) {return ($r, --$c)} elsif ($dir == SE) {return (--$r, --$c)} elsif ($dir == S) {return (--$r, $c)} elsif ($dir == SW) {return (--$r, ++$c)} elsif ($dir == W) {return ($r, ++$c)} elsif ($dir == NW) {return (++$r, ++$c)} else { die "Inside 'stepback': invalid param\n"; } } sub read_file { my ($ltrs, $words) = @_; @ARGV = @ARGV ? @ARGV : ("wp.txt"); # Read in grid of letters my @tmp; while (<>) { last if /^$/; chomp; @tmp = (); for ("\\", split(//, $_), "\\") { push @tmp, { data => $_, solution => FALSE, } } push @$ltrs, [@tmp]; } # Create border my $cols = @tmp; @tmp = (); for (1..$cols) { push @tmp, { data => "\\", solution => FALSE, } } unshift @$ltrs, [@tmp]; # Add Top border push @$ltrs, [@tmp]; # Add Bottom border # Read in words to search for while (<>) { chomp; push @$words, { data => $_, chars => [split //], found => FALSE, beg => [], end => [], } } } sub search { my ($ltrs, $words) = @_; my $end_row = $#{$ltrs} - 1; my $end_col = $#{$ltrs->[0]} - 1; for my $i ( 1..$end_row ) { for my $j ( 1..$end_col ) { for my $idx (0..$#words) { if (! $words->[$idx]{found} && $words->[$idx]{chars}[0] eq $ltrs->[$i][$j]{data +} ) { my ($rows, $cols) = ($i, $j); for my $dir (N..NW) { my $k = 0; ($rows, $cols) = step($dir, $rows, $cols); look($dir, $ltrs, $words, $rows, $cols, $idx, +$k); if (! $words->[$idx]{found} ) { $rows = $i; $cols = $j; } else { $ltrs->[$i][$j]{solution} = TRUE; $words->[$idx]{beg}[R] = $i; $words->[$idx]{beg}[C] = $j; last; } } } } } } } sub look { my ($dir, $ltrs, $words, $row, $col, $idx, $k) = @_; if (++$k != @{ $words->[$idx]{chars} } && $ltrs->[$row][$col]{data} eq $words->[$idx]{chars}[$k] ) { ($row, $col) = step($dir, $row, $col); look($dir, $ltrs, $words, $row, $col, $idx, $k); if ( $words->[$idx]{found} ) { ($row, $col) = stepback($dir, $row, $col); $ltrs[$row][$col]{solution} = TRUE; } } else { if ($k == @{ $words->[$idx]{chars} } ) { $words[$idx]{found} = TRUE; ($row, $col) = stepback($dir, $row, $col); $words->[$idx]{end}[R] = $row; $words->[$idx]{end}[C] = $col; } } } sub writefile { my ($ltrs, $words) = @_; # Print out puzzle letter grid for (my $i = 1; $i < $#{ $ltrs }; ++$i) { for (my $j = 1; $j < $#{ $ltrs->[0] }; ++$j) { print $ltrs->[$i][$j]{data} . ' '; } print "\n"; } # List search words & their location in the grid for (@$words) { print $_->{data}, "\t"; print "R" . $_->{beg}[R] . ' ' . "C" . $_->{beg}[C] . "\t"; print "R" . $_->{end}[R] . ' ' . "C" . $_->{end}[C] . "\n"; } # Print out grid solution for (my $i = 1; $i < $#{ $ltrs }; ++$i) { for (my $j = 1; $j < $#{ $ltrs->[0] }; ++$j) { if ($ltrs->[$i][$j]{solution}) { print $ltrs->[$i][$j]{data} . ' '; } else { print "-" . ' '; } } print "\n"; } } __DATA__ SSOKGCYUQMIEAWT PLIEBXUQNJGDZWT YQGNKHDAXURPMJG ETIHWRONGDAXVSQ NKPIRFADAXVTQOM JHFMACAEYWURPNL JHFDEBZGLLUBXVT RQOMPKIIECHCDFD BZYWSVGBVRORATR QONLKHIHEOAFEDC AZXWTFOSLHVBDDU BARE BIG CLEAR COOL DEAD EMPTY HARD LEVEL LIGHT PIG RED SOFT SPEAR WHITE WRONG

Replies are listed 'Best First'.
Re: Word Search Puzzle
by QM (Parson) on Jan 24, 2004 at 03:49 UTC
    Data structures created some pretty intense code and I was surprised that the C++ solution was cleaner in some ways.
    And dirtier in others ;)

    For instance, this:

    sub step { my ($dir, $r, $c) = @_; if ($dir == N) {return (--$r, $c)} elsif ($dir == NE) {return (--$r, ++$c)} elsif ($dir == E) {return ($r, ++$c)} elsif ($dir == SE) {return (++$r, ++$c)} elsif ($dir == S) {return (++$r, $c)} elsif ($dir == SW) {return (++$r, --$c)} elsif ($dir == W) {return ($r, --$c)} elsif ($dir == NW) {return (--$r, --$c)} else { die "Inside 'step': invalid param\n" } }
    This sub could easily be rewritten as a hash lookup (since it's just a big lookup table anyway):
    our %step = ( N => { R => -1, C => 0 }, NE => { R => -1, C => +1 }, E => { R => 0, C => +1 }, SE => { R => +1, C => +1 }, S => { R => +1, C => 0 }, SW => { R => +1, C => -1 }, W => { R => 0, C => -1 }, NW => { R => -1, C => -1 } );
    Then instead of this:
    ($rows, $cols) = step($dir, $rows, $cols);
    use this:
    $rows += $step{$dir}{R}; $cols += $step{$dir}{C};
    For free, sub stepback is gone, and instead of the call use this:
    $rows -= $step{$dir}{R}; $cols -= $step{$dir}{C};
    [step and stepback are essentially the same.]

    You don't need all of those use constant N => 0; either -- the string literal is just as good as a constant.

    There are probably a few more optimizations that could be made. For instance, what if you're given a grid, but instead of "the" word list, your given a lexicon? Then in search, the inner loop:

    for my $idx (0..$#words)
    will take quite some time. [BTW, $#words doesn't refer to the sub's my $word, but to the my @words declared at file scope. Bonus points for knowing why ;) ]

    The first optimization I would try would be to remove each entry from @words when the search is successful, and move the info to @words_found. Then later searches on @words would only look for the remaining words. This would also speed it up in the current incarnation, but perhaps only marginally.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

      This sub could easily be rewritten as a hash lookup (since it's just a big lookup table anyway):
      our %step = ( N => { R => -1, C => 0 }, NE => { R => -1, C => +1 }, E => { R => 0, C => +1 }, SE => { R => +1, C => +1 }, S => { R => +1, C => 0 }, SW => { R => +1, C => -1 }, W => { R => 0, C => -1 }, NW => { R => -1, C => -1 } );
      Nice solution - eliminates nearly identical function. I guess I was following my original C++ code.

      You don't need all of those use constant N => 0; either -- the string literal is just as good as a constant.

      I'm not clear on this :-)

      BTW, $#words doesn't refer to the sub's my $word, but to the my @words declared at file scope. Bonus points for knowing why ;)

      oops!

        You don't need all of those use constant N => 0; either -- the string literal is just as good as a constant.
        I'm not clear on this :-)
        You have
        use constant N => 0;
        and later
        if ($dir == N) {return (--$r, $c)} elsif ... else { die "Inside 'step': invalid param\n" }
        use constant... essentially gives you a subroutine-ish entity (without a sigil). If you miss the constant declaration, you'll be looking for a subroutine definition for N, NE,, etc. Still, some people like this.

        The benefits of declaring these constants are:
        • difficult to accidentally reassign to constants
        • direction validation (with die...)
        • saving quotes in comparison (as in  if ( $dir eq "N" ))
        [Assuming that $dir is now a string.]

        The drawbacks are:
        • no obvious sub definition
        constant isn't perfectly constant
        • constants aren't interpolated in double quoted strings
        [See use constant for a complete rundown.]

        I guess what I'm really saying is that it's unfair to compare a literal translation between C and Perl. Each have their idiosynchracies, and will fall short of the "ideal" on some scales.

        Someone here could probably write up a Perlish version of this that was easier to follow, easier to maintain, and perhaps did a few nifty tricks to boot. Though it might run slower than C. :)

        -QM
        --
        Quantum Mechanics: The dreams stuff is made of

Re: Word Search Puzzle
by Anonymous Monk on Jan 27, 2004 at 14:57 UTC
    I'm not sure if that's what the code already does, but would it make sense to split the field of letters into a set of strings line-by-line for each direction and then grep that list? E.g. if we have a letter block like this:
    BWSK
    ASDW
    LDKS
    SDAD
    
    we would generate the following set of strings: 'BWSK', 'ASDW', ..., 'BALS', 'WSDD', 'SDKA', etc (include the diagonal ones as well if necessary). Then we can search through that.