<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/">
  <channel about="http://blog.gmane.org/gmane.comp.lang.perl.qotw.discuss">
    <title>gmane.comp.lang.perl.qotw.discuss</title>
    <link>http://blog.gmane.org/gmane.comp.lang.perl.qotw.discuss</link>
    <description/>
    <syn:updatePeriod>hourly</syn:updatePeriod>
    <syn:updateFrequency>1</syn:updateFrequency>
    <syn:updateBase>1901-01-01T00:00+00:00</syn:updateBase>
    <items>
      <rdf:Seq>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2644"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2643"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2642"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2641"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2640"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2639"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2638"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2637"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2636"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2635"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2634"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2633"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2632"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2631"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2630"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2629"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2628"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2627"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2626"/>
        <rdf:li rdf:resource="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2625"/>
      </rdf:Seq>
    </items>
    <image rdf:resource="http://gmane.org/img/gmane-25t.png"/>
    <textinput rdf:resource=""/>
  </channel>
  <image rdf:about="http://gmane.org/img/gmane-25t.png">
    <title>Gmane</title>
    <url>http://gmane.org/img/gmane-25t.png</url>
    <link>http://gmane.org</link>
  </image>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2644">
    <title>[QUIZ] Perl 'Hard' Quiz of the Whatever #2008-03-28 - Solving Kakuro</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2644</link>
    <description>IMPORTANT: Please do not post solutions, hints, or other spoilers
until at least 60 hours after the date of this message.  Thanks.

Kakuro (a.k.a Cross-sums) is a kind of puzzle game:

http://en.wikipedia.org/wiki/Kakuro

In it, one fills in squares in a crossword-like grid that sum to their sums. 
One can fill in the digits from 1 to 9, and no digit can be repeated twice.

Your object is that given a Kakuro board in a text format described below, 
then output the final solution.

You can find some sample layouts from kakuro.com here: 
http://www.shlomifish.org/Files/files/text/kakuro.com-layouts/ 

and there's a new daily puzzle everyday there. (Requires Flash) There are also 
some more layouts googleable or on the wikipedia page.

The layout has the following format:

1. It consists of $Height lines.

2. Each line has $Width squares. Whitespace inside each line is ignored.

3. Each square starts with [ and ends with ]. 

4. A square that contains a \ is a blocked square (i.e: a square that cannot 
have any digit). If a number appears to the left of the \ it is the sum of 
the digits below the square. If a number appears to the right of the \ it is 
the sum of the digits to the right of the square.

5. A square that contains a single digit is filled with this digit. Such 
digits may be used in the solutions or for hints.

-------------------

The examples on http://www.shlomifish.org/Files/files/text/kakuro.com-layouts/ 
should be the most illustrative.

The output should be the same with all the non-blocked square containing 
digits. 

Bonus points for:

1. Intermediate solutions.

2. Reasonings.

Good luck!

Shlomi Fish

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

I'm not an actor - I just play one on T.V.

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2008-03-27T22:38:08</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2643">
    <title>Re: [QUIZ] Perl 'Medium' Quiz of the Whatever #2008-02-28 - Kakuro Digit Sums</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2643</link>
    <description>
[ Haven't got the original mail, so piggying here ]

"Any programming problem can be solved by adding another layer of 
indirection".

Here's my solution. Run this program to produce a program which 
satisfies the requirements. I also expect this solution to be the 
fastest around.... :-)

Regards,
M4


#!/usr/bin/perl

use strict;
use warnings;

sub get_digits_sum {
  my ($sum, $num_places) = &lt; at &gt;_;
  return [solve($sum, $num_places, 0, 0)];
}

sub solve {
  my ($sum, $places_left, $running, $previous, &lt; at &gt;partial) = &lt; at &gt;_;
  return map solve($sum, $places_left-1, $running+$_, $_, &lt; at &gt;partial, $_),
    ($previous+1..($sum - $running &gt; 9 ? 9 : $sum - $running))
      if $places_left;
  return $sum == $running ? [&lt; at &gt;partial] : ();
}


print &lt;&lt;'EOT';
#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests =&gt; 8;

my %sol = (
EOT

use Data::Dumper;
for my $sum (1..45) {
  print "$sum =&gt; {\n";
  for my $places (1..9) {
    my $answer = get_digits_sum($sum, $places);
    if (&lt; at &gt;$answer) {
      print "  $places =&gt; \n";
      my $t = Dumper($answer);
      $t =~ s/\$VAR1\s*=\s*/\t/;
      $t =~ s/;/,/;
      print "$t\n";
    }
  }
  print "},\n\n";
}


print &lt;&lt;'EOT';
);

sub get_digits_sum {
  my ($sum, $num_places) = &lt; at &gt;_;
  return exists $sol{$sum} &amp;&amp; exists $sol{$sum}{$num_places} ?
    $sol{$sum}{$num_places} : [];
}

my &lt; at &gt;tests = (
         [5,1, [[5]], "5 over 1"],

         [15,1, [], "15 over 1"],

         [3,2, [[1,2]], "3 over 2"],

         [7,3, [[1,2,4]], "7 over 3"],

         [15,5, [[1,2,3,4,5]], "15 over 5"],

         [25,5,
          [
           [1,2,5,8,9],
           [1,2,6,7,9],
           [1,3,4,8,9],
           [1,3,5,7,9],
           [1,3,6,7,8],
           [1,4,5,6,9],
           [1,4,5,7,8],
           [2,3,4,7,9],
           [2,3,5,6,9],
           [2,3,5,7,8],
           [2,4,5,6,8],
           [3,4,5,6,7],
          ],
          "25 over 5",
         ],

         [14,2, [[5,9],[6,8],], "14 over 2",],

         [99,2, [], "99 over 2",],
        );


for (&lt; at &gt;tests) {
  my ($sum, $digits, &lt; at &gt;x) = &lt; at &gt;$_;
  my $got = get_digits_sum($sum, $digits);
  is_deeply($got, &lt; at &gt;x);
}

EOT





</description>
    <dc:creator>Martijn Lievaart</dc:creator>
    <dc:date>2008-03-07T08:34:14</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2642">
    <title>Re: [QUIZ] Perl 'Medium' Quiz of the Whatever #2008-02-28 - Kakuro Digit Sums</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2642</link>
    <description>In Haskell, FWIW:

    get_digits_sum _ 0 = []
    get_digits_sum 0 _ = []
    get_digits_sum goal n = find goal n [1..9]
      where find _ _ [] = []
            find goal n (d:ds)
              | n == 1 &amp;&amp; d == goal = [[d]]
              | otherwise = with ++ find goal n ds
                  where with = map (d:) $ find (goal - d) (n - 1) ds

Greg

</description>
    <dc:creator>Greg Bacon</dc:creator>
    <dc:date>2008-03-06T14:44:52</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2641">
    <title>Re: [QUIZ] Perl 'Medium' Quiz of the Whatever #2008-02-28 - Kakuro Digit Sums</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2641</link>
    <description>
Here's my solution.  It, too, is recursive, but unlike M. Fish's, I've
used a secondary accumulator parameter.

    -- michael.

--
#!perl
use strict;
sub get_digits_sum {
    my ($sum, $left, $soln, &lt; at &gt;ns) = &lt; at &gt;_;
    return 
if $sum == 0;
    return [get_digits_sum($sum, $left, [], 1..9)] 
if !defined $soln;
    return (grep {$sum==$_} &lt; at &gt;ns) ? [&lt; at &gt;$soln, $sum] : () 
if $left == 1;
    my &lt; at &gt;rv;
    for my $n (&lt; at &gt;ns) {
return &lt; at &gt;rv 
    if $n &gt; $sum; # rest won't work either
push &lt; at &gt;rv, get_digits_sum($sum-$n, $left-1, [&lt; at &gt;$soln, $n], ($n+1)..9);
    }
    return &lt; at &gt;rv;
}
my ($sum, $places) = &lt; at &gt;ARGV;
my $rv = get_digits_sum($sum, $places);
print join " ", &lt; at &gt;$_, "\n" for &lt; at &gt;$rv;

</description>
    <dc:creator>michael-pG79k+jjugCvVpAfWQikzw&lt; at &gt;public.gmane.org</dc:creator>
    <dc:date>2008-03-06T10:05:11</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2640">
    <title>Re: [QUIZ] Perl 'Medium' Quiz of the Whatever #2008-02-28 - KakuroDigit Sums</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2640</link>
    <description>
my recursive solution:
8&lt;----8&lt;
#!/usr/bin/perl

use warnings;
use strict;
use Data::Dumper;

sub get_digits_sum {
my ($sum, $num, $start) = &lt; at &gt;_;
my $max = ($sum &lt; 10 ? $sum : 9);
$start = 1 unless $start;

return [] unless $num;
return [] if $sum &lt;= 0;
return [] if $max &lt; $start;
return [[$sum]] if $num == 1;

my &lt; at &gt;result;

for my $current ($start..$max) {
push &lt; at &gt;result, map [$current, &lt; at &gt;$_], &lt; at &gt;{get_digits_sum($sum - $current, $num - 1, $current + 1)};
}

return \&lt; at &gt;result;
}

die ("run with sum and number of cell's") 
unless &lt; at &gt;ARGV == 2;
print Dumper(get_digits_sum(&lt; at &gt;ARGV));
8&lt;----8&lt;


</description>
    <dc:creator>Premysl Anydot Hruby</dc:creator>
    <dc:date>2008-03-03T17:25:03</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2639">
    <title>Re: [QUIZ] Perl 'Medium' Quiz of the Whatever #2008-02-28 - Kakuro Digit Sums</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2639</link>
    <description>
Here is my solution, which is recursive:

&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
use strict;
use warnings;

use Test::More tests =&gt; 5;

my $max_digit = 9;
my $min_digit = 1;

sub _digits_sum_from
{
    my ($start, $sum, $num_places) = &lt; at &gt;_;

    if ($num_places == 1)
    {
        if (($sum &gt;= $start) &amp;&amp; ($sum &lt;= $max_digit))
        {
            return [[$sum]];
        }
        else
        {
            return [];
        }
    }

    my &lt; at &gt;results;
    FIRST_LOOP:
    foreach my $first ($start .. $max_digit)
    {
        if ($sum-$first &lt;= 0)
        {
            last FIRST_LOOP;
        }
        push &lt; at &gt;results, 
            (map 
                { [$first,&lt; at &gt;$_] }
                &lt; at &gt;{_digits_sum_from($first+1, $sum-$first, $num_places-1)}
            );
    }
    return \&lt; at &gt;results;
}

sub get_digits_sum
{
    my ($sum, $num_places) = &lt; at &gt;_;

    return _digits_sum_from($min_digit, $sum, $num_places);
}

# TEST
is_deeply(get_digits_sum(3,2), [[1,2]], "3 over 2");

# TEST
is_deeply(get_digits_sum(7,3), [[1,2,4]], "7 over 3");

# TEST
is_deeply(get_digits_sum(15,5), [[1,2,3,4,5]], "15 over 5");

# TEST
is_deeply(get_digits_sum(25,5),
    [
        [1,2,5,8,9],
        [1,2,6,7,9],
        [1,3,4,8,9],
        [1,3,5,7,9],
        [1,3,6,7,8],
        [1,4,5,6,9],
        [1,4,5,7,8],
        [2,3,4,7,9],
        [2,3,5,6,9],
        [2,3,5,7,8],
        [2,4,5,6,8],
        [3,4,5,6,7],
    ],
    "25 over 5",
);

# TEST
is_deeply(get_digits_sum(14,2), [[5,9],[6,8],], "14 over 2",);

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

I'm not an actor - I just play one on T.V.

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2008-03-03T15:04:36</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2638">
    <title>Re: [QUIZ] Perl 'Hard' Quiz of the Whatever #2008-12-28 - Symmetric Sokoban</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2638</link>
    <description>
Oops, and I found a bug in that logic. It's not just an optimization;
some kind of state cache is necessary to avoid just pushing the same
block back and forth forever. However, the cache currently ignores the
position of the player.

THAT is an optimization; given these two boards:

  A) #######   B) #######
     #     #      #     #
     # $ $ #      # $   #
     #     ###    # $   ###
     #     ..#    #     ..#
     #########    #########

There are multiple sets of pushes that will get you from A to B, and
no reason to try more than one set that gets you to the same result.
No matter where the player is once you've entered state B, the
possible future moves from there are all the same.

HOWEVER, if you take blocking into account:

  C) #####     D) #####
     #   #        #   #
     # #$#        # #$#
     # # ###      # # ###
     # $&lt; at &gt;..#      #&lt; at &gt;$ ..#
     #######      #######
    
States C and D are NOT equivalent -- one is solvable, and one isn't!
So if you ignore the position of the player entirely, that leads to
some valid (and possibly essential) moves being ignored.

I tried a quick fix, to add the player position to the state cache,
but that adds an order of magnitude to both the time and the number of
pushes in the solution.

What you really want is to consider C above equivalent to E below:

  E) #####
     #   #
     # #$#
     # #&lt; at &gt;###
     # $ ..#
     #######

because the available pushes from there are the same, but you don't
want to consider C or E equivalent to D.

--
Ron Isaacson
Morgan Stanley
ron.isaacson-/PgpppG8B+R7qynMiXIxWgC/G2K4zDHf&lt; at &gt;public.gmane.org / (212) 276-1144

</description>
    <dc:creator>Ron Isaacson</dc:creator>
    <dc:date>2008-01-06T04:17:12</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2637">
    <title>Re: [QUIZ] Perl 'Hard' Quiz of the Whatever #2008-12-28 - Symmetric Sokoban</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2637</link>
    <description>[NB: Sorry if anyone is getting multiple copies of this. The mail
server seems to keep rejecting my mail due to attachment content type,
so the program is now included in the message.]

Jurgen Pletinckx wrote:

Same here, on both counts. :-) I just finished mine, and haven't
looked at either of the two posted solutions yet, so I'm not sure how
they compare.

My general approach is pretty simple:

  - Find all valid pushes from the current spot
  - For each push:
      - Do it
      - Recurse
      - If the puzzle is solved, exit
      - Undo it

The puzzle is always stored and used as a list of lists, with
$puzzle-&gt;[$row]-&gt;[$col] containing the actual ASCII character, and
it's never converted into any other representation. Nor is it ever
rotated or transformed in any other way (other than making moves &amp;
then reversing them).

The "find all valid pushes" piece was easier than I originally made it
out to be. I eventually settled on a non-recursive version of the
"flood fill" algorithm to radiate outward from the current point,
looking for all spots from which a box can be pushed, and storing the
path I took to get to each one.

I currently make no attempt to minimize either the number of pushes or
the total number of moves. My program is guaranteed to find a solution
if one exists, but it will almost definitely not be the minimal
solution. It also moves in a predictable order (left/right/up/down),
and will find the same solution every time, unless given the -r
command-line option (to randomize moves).

There are a few minor optimizations made at each step, like not
putting the puzzle into any state it's been in before, and not going
any further once the puzzle has been made unsolveable. Determining
"solvable" is where the real speed gains can be made. My test is
fairly simple -- just make sure each box has a position from which it
can be pushed. A more comprehensive approach would be to test for all
of the "trap" positions under SOKOBAN'S IMPOSSIBLE MOVES at:

  http://www.geocities.com/erimsever/sokoban1.htm

This would allow entire move sets to be pruned fairly quickly.

Anyway, code is below, with copious comments throughout. It supports a
few command-line arguments, mostly related to the display. If you
watch it move (the default), you'll see lots of unsolveable positions
it gets itself into, especially in -r mode.

Run with -q (the fastest option), it solves the given puzzle in about
7.2 seconds on my 3.4 GHz Linux desktop. But it takes 1192 moves / 352
pushes, which can definitely be reduced.

Just to make sure it worked, I also ran puzzle #1 from the "standard"
collection:

      #####
      #   #
      #$  #
    ###  $##
    #  $ $ #
  ### # ## #   ######
  #   # ## #####  ..#
  # $  $          ..#
  ##### ### #&lt; at &gt;##  ..#
      #     #########
      #######

It finished this one in 2.65s, using 432 moves / 161 pushes. For fun,
I tried puzzle #2 from that collection:

  ############
  #..  #     ###
  #..  # $  $  #
  #..  #$####  #
  #..    &lt; at &gt; ##  #
  #..  # #  $ ##
  ###### ##$ $ #
    # $  $ $ $ #
    #    #     #
    ############

But after about 12 hours, I Ctrl-C'ed it. :-) I think the additional
"unsolvable" conditions would need to be added to solve this one in a
reasonable amount of time.

--
Ron Isaacson
Morgan Stanley
ron.isaacson-/PgpppG8B+R7qynMiXIxWgC/G2K4zDHf&lt; at &gt;public.gmane.org / (212) 276-1144

-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----8&lt;-----

#!/usr/bin/perl

use strict;
use Getopt::Long;
use POSIX;
use Term::Cap;
use Time::HiRes qw(time sleep);
use List::Util  qw(shuffle);

# Constants

our ($LEFT, $RIGHT, $UP, $DOWN)                       = (-1, 1, -2, 2);
our ($WALL, $BOX, $TARGET, $FTARGET, $PLAYER, $EMPTY) = split //, '#$.X&lt; at &gt; ';

our %MOVE =
  (
   $LEFT  =&gt; 'l',
   $RIGHT =&gt; 'r',
   $UP    =&gt; 'u',
   $DOWN  =&gt; 'd',
  );

# Globals

our $TERM;
our $QUIET  = 0;
our $DELAY  = 0.1;
our $RANDOM = 0;

######
##
## Terminal control
##
######

sub init_term {
  # From Term::Cap docs

  my $termios = POSIX::Termios-&gt;new;

  $termios-&gt;getattr;
  my $ospeed = $termios-&gt;getospeed;

  $TERM = Tgetent Term::Cap { TERM =&gt; undef, OSPEED =&gt; $ospeed };
  $TERM-&gt;Trequire (qw(UP md me));
}

sub move_up {
  my ($lines) = &lt; at &gt;_;

  print $TERM-&gt;Tgoto ('UP', undef, $lines);
}

sub highlight {
  my ($char) = &lt; at &gt;_;

  return
    ($TERM-&gt;Tputs ('md') .
     $char .
     $TERM-&gt;Tputs ('me'));
}

######
##
## Puzzle management
##
######

# Read puzzle from file

sub read_puzzle {
  my ($file) = &lt; at &gt;_;
  my $puzzle = [];

  open FILE, $file
    or return;

  while (&lt;FILE&gt;) {
    chomp;
    push &lt; at &gt;$puzzle, [split //, $_];
  }

  close FILE;
  return $puzzle;
}

# Find player's initial position

sub starting_point {
  my ($puzzle) = &lt; at &gt;_;

  for my $row (0..$#{$puzzle}) {
    for my $col (0..$#{$puzzle-&gt;[$row]}) {
      if ($puzzle-&gt;[$row]-&gt;[$col] eq $PLAYER) {
        $puzzle-&gt;[$row]-&gt;[$col] = $EMPTY;
        return ($row, $col);
      }
    }
  }

  return;
}

# Make sure puzzle has enough targets for all of the free boxes

sub has_enough_targets {
  my ($puzzle) = &lt; at &gt;_;

  my %count;

  for my $row (0..$#{$puzzle}) {
    for my $col (0..$#{$puzzle-&gt;[$row]}) {
      $count{$puzzle-&gt;[$row]-&gt;[$col]}++;
    }
  }

  return ($count{$TARGET} &gt;= $count{$BOX});
}

# String representation of puzzle's current state

sub puzzle_state {
  my ($puzzle) = &lt; at &gt;_;

  my $state = join "\n", map { join "", &lt; at &gt;$_ } &lt; at &gt;$puzzle;
  return $state;
}

# Check to see if the puzzle is solved

sub puzzle_solved {
  my ($puzzle) = &lt; at &gt;_;

  for my $row (0..$#{$puzzle}) {
    for my $col (0..$#{$puzzle-&gt;[$row]}) {
      return if $puzzle-&gt;[$row]-&gt;[$col] eq $TARGET;
    }
  }

  return 1;
}

# Display puzzle, possibly including the current number of pushes
# (stack depth) on the first line, then reposition the cursor at the
# top for a redisplay

sub show_puzzle {
  my ($puzzle, $player_row, $player_col, $pushes) = &lt; at &gt;_;

  for my $row (0..$#{$puzzle}) {
    my $line;

    for my $col (0..$#{$puzzle-&gt;[$row]}) {
      $line .=
        (($row == $player_row and $col == $player_col) ?
         $PLAYER :
         $puzzle-&gt;[$row]-&gt;[$col]);
    }

    # Draw boxes &amp; targets in bold to make them stand out

    $line =~ s|([$BOX$TARGET$FTARGET]+)|highlight ($1)|ge;

    if ($row == 0) {
      $line .= sprintf "  %-10d", $pushes
        if $pushes;
    }

    print "$line\n";
  }

  move_up (scalar &lt; at &gt;$puzzle);
}

# Move the cursor back down to the bottom of the puzzle, show a
# message and exit

sub final_msg {
  my ($puzzle, $msg, $code) = &lt; at &gt;_;

  print "\n" x scalar &lt; at &gt;$puzzle;
  print "\n$msg\n\n";
  exit $code;
}

######
##
## Motion
##
######

# Position state checks

sub is_pushable   { $_[0] eq $BOX   or $_[0] eq $FTARGET }
sub is_occupiable { $_[0] eq $EMPTY or $_[0] eq $TARGET  }

# Make sure we don't leave the puzzle. The playing area should be
# surrounded by walls, but we never actually verify that.

sub out_of_bounds {
  my ($puzzle, $row, $col) = &lt; at &gt;_;

  return
    ($row &lt; 0 or $row &gt;= $#{$puzzle} or
     $col &lt; 0 or $col &gt;= $#{$puzzle-&gt;[$row]});
}

# Get the piece at a certain spot

sub get_piece {
  my ($puzzle, $row, $col) = &lt; at &gt;_;

  return
    (out_of_bounds ($puzzle, $row, $col) ?
     undef                               :
     $puzzle-&gt;[$row]-&gt;[$col]);
}

# Given a starting point and a direction, find the next two pieces in
# that direction. Return the row/column index and the current piece in
# each position.

sub move_data {
  my ($puzzle, $row, $col, $move) = &lt; at &gt;_;

  my ($row1, $col1);
  my ($row2, $col2);

  # If the move is 2 or -2, we're going vertically, otherwise
  # horizontally

  if ($move % 2 == 0) {
    my $nmove = $move / 2;

    $row1 = $row + $nmove;
    $col1 = $col;

    $row2 = $row + ($nmove * 2);
    $col2 = $col;
  } else {
    $row1 = $row;
    $col1 = $col + $move;

    $row2 = $row;
    $col2 = $col + ($move * 2);
  }

  my $piece1 = get_piece ($puzzle, $row1, $col1);
  my $piece2 = get_piece ($puzzle, $row2, $col2);

  return
    ($row1, $col1, $piece1,
     $row2, $col2, $piece2);
}

# Check to see if a certain move would result in a valid box push

sub can_push {
  my ($puzzle, $row, $col, $move) = &lt; at &gt;_;

  my ($row1, $col1, $piece1,
      $row2, $col2, $piece2) = move_data ($puzzle, $row, $col, $move);

  # The piece must be pushable, and the spot it's going must be
  # occupiable

  return
    (is_pushable   ($piece1) and
     is_occupiable ($piece2));
}

# Check to see if a box is "stuck", meaning that it can never be
# pushed

sub is_stuck {
  my ($puzzle, $row, $col) = &lt; at &gt;_;

  # If a box can be pushed now, then it's not stuck and we're done. 
  # But if a box is blocked in by other boxes, then it's only stuck if
  # all of those boxes are stuck too. Otherwise a push on one of those
  # might make this one on-stuck.
  #
  # We'll use a standard "flood-fill" algorithm to efficiently find
  # all boxes touching the current box. As soon as we find one that's
  # pushable, we're done.

  my &lt; at &gt;queue;
  my %seen;

  push &lt; at &gt;queue, [$row, $col];

  while (&lt; at &gt;queue) {
    my $next        = shift &lt; at &gt;queue;
    my ($row, $col) = &lt; at &gt;$next;

    # Mark the boxes we've already checked so we don't go backwards

    $seen{$row,$col} = $WALL;

    # Go one spot in each direction, and see if the box can be pushed
    # from there

    for my $move ($LEFT, $RIGHT, $UP, $DOWN) {
      my ($row1, $col1) = move_data ($puzzle, $row, $col, $move);
      my $piece1        = $seen{$row1,$col1} || $puzzle-&gt;[$row1]-&gt;[$col1];

      # To push back on the piece we just left, we'll try the OPPOSITE
      # move of the one we made to get here, which is easily done by
      # negating $move

      return if
        (is_occupiable ($piece1) and
         can_push      ($puzzle, $row1, $col1, -$move));

      push &lt; at &gt;queue, [$row1, $col1]
        if ($piece1 eq $BOX);
    }
  }

  # We can't push this box or any of its neighbors, so it must be
  # stuck

  return 1;
}

# Check to ensure the puzzle is solvable

sub is_solvable {
  my ($puzzle) = &lt; at &gt;_;

  # If there are any stuck boxes (see above), the puzzle is unsolvable

  for my $row (0..$#{$puzzle}) {
    for my $col (0..$#{$puzzle-&gt;[$row]}) {
      next unless
        ($puzzle-&gt;[$row]-&gt;[$col] eq $BOX);

      return if
        is_stuck ($puzzle, $row, $col);
    }
  }

  # Note that this doesn't guarantee that the puzzle is solvable --
  # there are still other conditions that can make it unsolvable.
  # Adding more checks will probably make the program faster.

  return 1;
}

# Find all pushes that can be made from the current position

sub find_pushes {
  my ($puzzle, $row, $col) = &lt; at &gt;_;

  # We're starting out in empty space, and looking for all of the
  # reachable positions from which a box can be pushed. As in
  # is_stuck, we'll use the "flood-fill" algorithm to find all
  # neighboring empty space, and in each position, check all four
  # directions to see if there's a pushable box in that direction.
  #
  # Unlike in is_stuck, we're building up a list of ALL pushes we can
  # make from here, so don't stop until we've searched all reachable
  # empty space.
  #
  # While we're at it, we'll build up a path to each push, which will
  # allow us to present a complete set of moves at the end.
  #
  # Note that neither the set of pushes nor the set of moves is likely
  # to be minimal. The set of moves could be reduced by applying
  # Dijkstra's algorithm on the empty space maps once the exact set of
  # pushes is found.

  my &lt; at &gt;pushes;
  my &lt; at &gt;queue;
  my %seen;

  push &lt; at &gt;queue, [$row, $col];

  while (&lt; at &gt;queue) {
    my $next               = shift &lt; at &gt;queue;
    my ($row, $col, &lt; at &gt;prev) = &lt; at &gt;$next;

    # Mark the spaces we've already been to so we don't go backwards

    $seen{$row,$col} = $WALL;

    # In each direction, we might find a box we can push, or more
    # empty space we can walk into

    my &lt; at &gt;moves = ($LEFT, $RIGHT, $UP, $DOWN);
    &lt; at &gt;moves    = shuffle &lt; at &gt;moves if $RANDOM;

    for my $move (&lt; at &gt;moves) {
      # Can we push in this direction?

      push &lt; at &gt;pushes, [$row, $col, $move, &lt; at &gt;prev]
        if can_push ($puzzle, $row, $col, $move);

      # Can we walk in this direction?

      my ($row1, $col1) = move_data ($puzzle, $row, $col, $move);
      my $piece1        = $seen{$row1,$col1} || $puzzle-&gt;[$row1]-&gt;[$col1];

      push &lt; at &gt;queue, [$row1, $col1, &lt; at &gt;prev, $move]
        if is_occupiable ($piece1);
    }
  }

  return &lt; at &gt;pushes;
}

# Try to push a box. If successful, return the new position (and a way
# to undo the push, which we'll need later) -- otherwise return
# nothing.
#
# Note that we assume all moves we're given have already passed the
# can_push test, so we're looking for other conditions that would make
# this push invalid.

sub do_push {
  my ($puzzle, $states, $row, $col, $move) = &lt; at &gt;_;

  my ($row1, $col1, $piece1,
      $row2, $col2, $piece2) = move_data ($puzzle, $row, $col, $move);

  # Push the box first, and undo it later if something goes wrong

  $puzzle-&gt;[$row1]-&gt;[$col1] = $piece1 eq $FTARGET ? $TARGET  : $EMPTY;
  $puzzle-&gt;[$row2]-&gt;[$col2] = $piece2 eq $TARGET  ? $FTARGET : $BOX;

  my $undo = sub {
    $puzzle-&gt;[$row1]-&gt;[$col1] = $piece1;
    $puzzle-&gt;[$row2]-&gt;[$col2] = $piece2;
  };

  # Make sure we didn't just make the puzzle unsolveable

  unless (is_solvable ($puzzle)) {
    $undo-&gt;();
    return;
  }

  # The puzzle should be in a new state that it hasn't been in before.
  # Otherwise, we might just push one box back and forth forever.

  my $state = puzzle_state ($puzzle);

  if (exists $states-&gt;{$state}) {
    $undo-&gt;();
    return;
  }

  # Ok, push successful

  return ($row1, $col1, $undo);
}

######
##
## Solver
##
######

# Recursive solver

sub solve_recursive {
  my ($puzzle, $row, $col, $states, $pushes) = &lt; at &gt;_;

  # Simple brute-force algorithm:
  #
  #   - Find all valid pushes from the current spot
  #   - For each push:
  #       - Do it
  #       - Recurse
  #       - Undo it

  my &lt; at &gt;available_pushes = find_pushes ($puzzle, $row, $col);

  for my $push (&lt; at &gt;available_pushes) {
    my ($row, $col, $move, &lt; at &gt;prev) = &lt; at &gt;$push;

    my ($new_row, $new_col, $undo) =
      do_push ($puzzle, $states, $row, $col, $move)
        or next;

    push &lt; at &gt;$pushes, $push;

    # Save the state, to make sure we don't end up back here again

    my $state = puzzle_state ($puzzle);
    $states-&gt;{$state} = 1;

    # Check to wee if we're done

    my $solved = puzzle_solved ($puzzle);

    if ($solved or not $QUIET) {
      show_puzzle ($puzzle, $new_row, $new_col, scalar &lt; at &gt;$pushes);

      return 1      if $solved;
      sleep  $DELAY if $DELAY;
    }

    # Down we go...

    solve_recursive ($puzzle, $new_row, $new_col, $states, $pushes)
      and return 1;

    # Oops, didn't work out

    pop &lt; at &gt;$pushes;
    $undo-&gt;();
  }

  return;
}

# Entry point

sub solve_puzzle {
  my ($puzzle, $row, $col) = &lt; at &gt;_;

  my $states = {};
  my $pushes = [];

  my $start  = time;
  my $solved = solve_recursive ($puzzle, $row, $col, $states, $pushes);
  my $end    = time;

  return unless $solved;

  return (1, $end - $start, $pushes);
}

# Create a complete list of moves from a list of pushes

sub list_moves {
  my (&lt; at &gt;pushes) = &lt; at &gt;_;

  my &lt; at &gt;moves;

  for my $push (&lt; at &gt;pushes) {
    my ($row, $col, $move, &lt; at &gt;prev) = &lt; at &gt;$push;

    push &lt; at &gt;moves, map { $MOVE{$_} } &lt; at &gt;prev;
    push &lt; at &gt;moves, uc $MOVE{$move};
  }

  return &lt; at &gt;moves;
}

###############################################################################

my $usage = &lt;&lt;USAGE;
Usage: $0 [-q | -d &lt;delay&gt;] [-r] puzzlefile

  -q          Quick mode -- don't show solutions in progress
  -d &lt;delay&gt;  Sleep for &lt;delay&gt; seconds between frames (0 or decimal ok)
  -r          Move randomly, instead of always left/right/up/down
USAGE

GetOptions
  (
   'q'   =&gt; \$QUIET,
   'd=s' =&gt; \$DELAY,
   'r'   =&gt; \$RANDOM,
  )
  or die $usage;

my $puzzle_file = shift
  or die $usage;

init_term;

my $puzzle =
  read_puzzle ($puzzle_file) or die "Error reading $puzzle_file: $!\n";

is_solvable ($puzzle)        or die "Puzzle started out unsolvable\n";
has_enough_targets ($puzzle) or die "Puzzle doesn't contain enough targets\n";

my ($row, $col) =
  starting_point ($puzzle)   or die "Puzzle contains no starting point\n";

show_puzzle ($puzzle, $row, $col);

my ($solved, $time, $pushes) = solve_puzzle ($puzzle, $row, $col);

if (not $solved) {
  final_msg ($puzzle, "No solution found", 1);
}

my &lt; at &gt;moves  = list_moves (&lt; at &gt;$pushes);
my $result = sprintf ("Solved in %d pushes (%d moves), %.2fs\n",
                      scalar &lt; at &gt;$pushes, scalar &lt; at &gt;moves, $time);

while (my &lt; at &gt;line = splice &lt; at &gt;moves, 0, 70, ()) {
  $result .= "\n";
  $result .= join "", &lt; at &gt;line;
}

final_msg ($puzzle, $result, 0);

</description>
    <dc:creator>Ron Isaacson</dc:creator>
    <dc:date>2008-01-06T02:47:10</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2636">
    <title>Re: [QUIZ] Perl 'Hard' Quiz of the Whatever #2008-12-28 - Symmetric Sokoban</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2636</link>
    <description>
Hi all!

OK, my complete solution to the QOTW can be found here:

http://www.shlomifish.org/Files/files/code/perl/qotw/Shlomif-Sokoban-Solver-0.01.tar.gz

It includes some tests (though, not for the solving algorithm itself), a 
module and some tests.

My scheme was:

1. Store the board as a vector of 2-bits, and the states as 2 bits (locations 
of the boxes and the space accessible to the player). vec() was used for the 
bit-fiddling.

2. Make use of the symmetry by only storing and looking up the least-valued 
rotated board. This makes it unsuitable for general Sokoban solutions, at 
least until I adapt the code to detect a possible symmetry.

3. Use a BFS search to find the solution, and a BFS search to find the 
accessible places for a player to go.

------------------

There's still room for many optimisations and many dead ends are not detected 
soon enough.

The program runs in just under two minutes on my Mandriva Cooker system on a 
P4-2.4GHz machine.

I'm including here the code of my main module which implements almost all of 
the logic of the solver.

Regards,

Shlomi Fish

&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
package Shlomif::Sokoban::Solver::Board;

use strict;
use warnings;

=head1 NAME

Shlomif::Sokoban::Solver::Board - a board for the sokosolver.

=head1 SYNOPSIS

For internal use by the Sokoban solver. See the test files.

=cut

use List::Util qw(max);

use Object::Tiny qw/
    height
    width
    _collect
    _data
    _dests
    _init_state
    _queue
/;

my $dest_place_bits = 0x1;
my $wall_bits = 0x2;

my $box_bits = 0x1;
my $reachable_bits = 0x2;

=head1 METHODS

=head2 load($board)

Loads a board in standard Sokoban notation.

=cut

sub _calc_offset
{
    my ($self, $x, $y) = &lt; at &gt;_;

    return $y*$self-&gt;width()+$x;
}

sub load
{
    my ($pkg, $contents) = &lt; at &gt;_;

    # Remove trailing whitespace.
    $contents =~ s{(\s*\n)+\z}{}ms; 

    # Remove trailing whitespace from lines.
    $contents =~ s{\s+$}{}gms;

    my &lt; at &gt;lines = (map { [ split(//, $_) ] } split(/\n/, $contents));

    my $data = "";

    my $init_state = "";
    my $init_pos;

    my $self = 
        $pkg-&gt;new(
            height =&gt; scalar(&lt; at &gt;lines),
            width =&gt; max(map { scalar(&lt; at &gt;$_) } &lt; at &gt;lines),
            _data =&gt; \$data,
            _dests =&gt; [],
            _init_state =&gt; \$init_state,
            _collect =&gt; +{},
            _queue =&gt; [],
        );



    foreach my $y (0 .. $#lines)
    {
        my $l = $lines[$y];

        foreach my $x (0 .. $#$l)
        {
            my $offset = $self-&gt;_calc_offset($x, $y);

            # Initialise the init_state block to the default.
            vec($init_state, $offset, 2) = 0;
            if ($l-&gt;[$x] eq "#")
            {
                vec(${$self-&gt;_data()}, $offset, 2) = $wall_bits;
            }
            elsif ($l-&gt;[$x] eq ".")
            {
                vec(${$self-&gt;_data()}, $offset, 2) = $dest_place_bits;
                push &lt; at &gt;{$self-&gt;_dests()}, [$x, $y];
            }
            else
            {
                vec(${$self-&gt;_data()}, $offset, 2) = 0;
                if ($l-&gt;[$x] eq '$')
                {
                    vec($init_state, $offset, 2) = $box_bits;
                }
                elsif ($l-&gt;[$x] eq '&lt; at &gt;')
                {
                    $init_pos = [$x, $y];
                }
            }
        }
    }

    if (!defined($init_pos))
    {
        die "The initial position of the player was not defined.";
    }

    $self-&gt;_mark_reachable(\$init_state, &lt; at &gt;$init_pos);

    return $self;
}

=head2 $board-&gt;is_wall($x,$y)

Returns if the block at the position $x,$y is a wall.

=cut

sub is_wall
{
    my ($self, $x, $y) = &lt; at &gt;_;

    return (vec(${$self-&gt;_data()}, $self-&gt;_calc_offset($x,$y), 2) == 
$wall_bits);
}

=head2 $board-&gt;is_dest($x,$y)

Returns if the block at the position $x,$y is a destination block.

=cut

sub is_dest
{
    my ($self, $x, $y) = &lt; at &gt;_;

    return (vec(${$self-&gt;_data()}, $self-&gt;_calc_offset($x,$y), 2)
            == $dest_place_bits
        );
}

=head2 $board-&gt;is_box($s_ref, $x, $y)

Is ($x,$y) in the state referenced by $s_ref a box?

=cut

sub is_box
{
    my ($self, $s_ref, $x, $y) = &lt; at &gt;_;
    return (vec($$s_ref, $self-&gt;_calc_offset($x,$y), 2) == $box_bits);
}

=head2 $board-&gt;is_reachable($s_ref, $x, $y)

Is ($x,$y) in the state referenced by $s_ref reachable by the player?

=cut

sub is_reachable
{
    my ($self, $s_ref, $x, $y) = &lt; at &gt;_;
    return (vec($$s_ref, $self-&gt;_calc_offset($x,$y), 2) == $reachable_bits);
}

sub _mark_reachable
{
    my ($self, $s_ref, $start_x, $start_y) = &lt; at &gt;_;

    # Breadth-first search to find all the reachable positions in the board.
    my &lt; at &gt;to_check =([$start_x, $start_y]);

    while (my $pos = shift(&lt; at &gt;to_check))
    {
        # Mark as reachable.
        vec($$s_ref, $self-&gt;_calc_offset(&lt; at &gt;$pos), 2) = $reachable_bits;

        foreach my $offset ([-1,0],[1,0],[0,-1],[0,1])
        {
            my &lt; at &gt;new_pos = ($pos-&gt;[0]+$offset-&gt;[0], $pos-&gt;[1]+$offset-&gt;[1]);
            if (   ($new_pos[0] &gt;= 0)
                &amp;&amp; ($new_pos[1] &gt;= 0)
                &amp;&amp; ($new_pos[0] &lt; $self-&gt;width())
                &amp;&amp; ($new_pos[1] &lt; $self-&gt;height())
                &amp;&amp; (! $self-&gt;is_wall(&lt; at &gt;new_pos))
                &amp;&amp; (! $self-&gt;is_box($s_ref, &lt; at &gt;new_pos))
                &amp;&amp; (! $self-&gt;is_reachable($s_ref, &lt; at &gt;new_pos))
               )
            {
                push &lt; at &gt;to_check, \&lt; at &gt;new_pos;
            }
        }
    }

    return;
}

sub _rotate
{
    my ($self, $s_ref) = &lt; at &gt;_;

    my $ret = "";

    my $width = $self-&gt;width()-1;
    my $height = $self-&gt;height()-1;

    for my $x (0 .. $width)
    {
        for my $y (0 .. $height)
        {
            vec($ret, $self-&gt;_calc_offset($y, $width-$x), 2) =
                vec($$s_ref, $self-&gt;_calc_offset($x,$y), 2);
        }
    }

    return \$ret;
}

# Get the minimal rotation permutation
sub _get_min_rot_perm
{
    my ($self, $s_ref) = &lt; at &gt;_;

    # Find the minimal board by its rotation permutations.
    my $min_rot_times = 0;
    my $min_rot_board = $s_ref;
    foreach my $r (1 .. 3)
    {
        my $new = $self-&gt;_rotate($s_ref);
        if ($$new lt $$min_rot_board)
        {
            $min_rot_times = $r;
            $min_rot_board = $new;
        }
        $s_ref = $new;
    }

    return ($min_rot_times, $min_rot_board);
}

sub _derive
{
    my ($self, $state_ref, $box_xy, $push_to_xy) = &lt; at &gt;_;

    my $new_state = "";

    for my $y (0 .. $self-&gt;height()-1)
    {
        for my $x (0 .. $self-&gt;width()-1)
        {
            my $offset = $self-&gt;_calc_offset($x, $y);
            if ($self-&gt;is_box($state_ref, $x, $y))
            {
                vec($new_state, $offset, 2) = $box_bits;
            }
            else
            {
                vec($new_state, $offset, 2) = 0;
            }
        }
    }

    # Move the new box.
    vec($new_state, $self-&gt;_calc_offset(&lt; at &gt;$box_xy), 2) = 0;
    vec($new_state, $self-&gt;_calc_offset(&lt; at &gt;$push_to_xy), 2) = $box_bits;

    # Mark the reachable bits.
    $self-&gt;_mark_reachable(\$new_state, &lt; at &gt;$box_xy);

    return \$new_state;
}

sub _output
{
    my ($self, $s_ref) = &lt; at &gt;_;

    for my $y (0 .. ($self-&gt;height()-1))
    {
        for my $x (0 .. ($self-&gt;width()-1))
        {
            print   $self-&gt;is_wall($x, $y) ? "#"
                  : $self-&gt;is_box($s_ref, $x, $y)  ? '$'
                  : $self-&gt;is_dest($x, $y) ? "."
                  : " "
                  ;
        }
        print "\n";
    }
    print "\n";
}

sub _is_final
{
    my ($self, $s_ref) = &lt; at &gt;_;

    foreach my $d (&lt; at &gt;{$self-&gt;_dests()})
    {
        if (! $self-&gt;is_box($s_ref, &lt; at &gt;$d))
        {
            return 0;
        }
    }
    return 1;
}

sub _try_to_move_box
{
    my ($self, $state_ref, $x, $y) = &lt; at &gt;_;

    for my $offset ([-1,0],[1,0],[0,-1],[0,1])
    {
        my &lt; at &gt;push_to = ($x+$offset-&gt;[0], $y+$offset-&gt;[1]);
        my &lt; at &gt;push_from = ($x-$offset-&gt;[0], $y-$offset-&gt;[1]);
        
        if (   (! $self-&gt;is_wall(&lt; at &gt;push_to))
            &amp;&amp; (! $self-&gt;is_box($state_ref, &lt; at &gt;push_to))
            &amp;&amp; $self-&gt;is_reachable($state_ref, &lt; at &gt;push_from)
           )
        {
            # We can push.
            my $new_state_ref =
                $self-&gt;_derive($state_ref, [$x, $y], \&lt; at &gt;push_to)
                ;

            # Print it - this is temporary for debugging.
            # (Now commented out.)
            # $self-&gt;_output($new_state_ref);

            # Else - register it and proceed.
            
            my ($rot_idx, $rot_state) =
                $self-&gt;_get_min_rot_perm($new_state_ref);
            if (exists($self-&gt;_collect()-&gt;{$$rot_state}))
            {
                # Do nothing
            }
            else
            {
                $self-&gt;_collect()-&gt;{$$rot_state} =
                {
                    r =&gt; (($rot_idx+$self-&gt;_collect()-&gt;{$$state_ref}-&gt;{r})%4),
                    p =&gt; $state_ref
                };
                if ($self-&gt;_is_final($rot_state))
                {
                    return $rot_state;
                }
                push &lt; at &gt;{$self-&gt;_queue()}, $rot_state;
            }
            
        }
    }

    return;
}

sub _trace_solution
{
    my ($self, $final_state) = &lt; at &gt;_;

    my &lt; at &gt;solution;

    {
        my $state = $final_state;

        while (defined($state))
        {
            push &lt; at &gt;solution, $state;
            $state = $self-&gt;_collect-&gt;{$$state}-&gt;{p};
        }
    }

    foreach my $state (reverse(&lt; at &gt;solution))
    {
        my $r = $self-&gt;_collect-&gt;{$$state}-&gt;{r};

        # Normalize the state from its rotated position.
        my $rot_state = $state;
        while ($r%4 != 0)
        {
            $rot_state = $self-&gt;_rotate($rot_state);
            $r++;
        }

        $self-&gt;_output($rot_state);
    }
}

=head2 $board-&gt;solve()

Actually solve the board.

=cut

sub solve
{
    my $self = shift;

    my $s_ref = $self-&gt;_init_state();

    my ($rot_idx, $rot_state) = $self-&gt;_get_min_rot_perm($s_ref);

    $self-&gt;_collect()-&gt;{$$rot_state} = { r =&gt; $rot_idx, p =&gt; undef() };

    push &lt; at &gt;{$self-&gt;_queue()}, $rot_state;

    my $w = $self-&gt;width()-1;
    my $h = $self-&gt;height()-1;

    while (my $state_ref = pop(&lt; at &gt;{$self-&gt;_queue()}))
    {
        for my $y (0 .. $h)
        {
            for my $x (0 .. $w)
            {
                if ($self-&gt;is_box($state_ref, $x, $y))
                {
                    my $final = $self-&gt;_try_to_move_box($state_ref, $x, $y);

                    if (defined($final))
                    {
                        $self-&gt;_trace_solution($final);

                        return $final;
                    }
                }
            }
        }
    }
}

=head2 width()

Returns the width of the board.

=head2 height()

Returns the height of the board.

=cut

1;
---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

I'm not an actor - I just play one on T.V.

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2008-01-04T17:13:16</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2635">
    <title>RE: [QUIZ] Perl 'Hard' Quiz of the Whatever #2008-12-28 - Symmetric Sokoban</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2635</link>
    <description>Shlomi Fish &lt;mailto:shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org&gt; mailed on 28 December 2007
12:31:

| IMPORTANT: Please do not post solutions, hints, or other spoilers
| until at least 60 hours after the date of this message.  Thanks.
| 
| I was told the "What does this code do?" quizzes were not as good as a
new
| programming task, so here's a more traditional QOTW that I came up
with. In
| this quiz you'll try to solve the following Sokoban (
| http://en.wikipedia.org/wiki/Sokoban ) puzzle using Perl:
| 
| {{{{{{{{{{{{{{
|   ####
|   #  #
|   #  ####
| ###$.$  #
| #  .&lt; at &gt;.  #
| #  $.$###
| ####  #
|    #  #
|    ####
| }}}}}}}}}}}}}}


Well past the spoiler line now ...

(I do prefer these tasks to "WDTCD?" quizzes. *And* I 
had some tuits over the holidays. Anyone else out there?)

I've tried not to be too clever. 

* Standard breadth-first search, no heuristics

* tree is stored in a hash, as child =&gt; parent pairs

* sparse tree (I only record the fastest way to get to a state)

* the text representation of a board state is used 
  for both move detection and for storing

* subroutine 'move' checks whether the sokoban can
  move to the right, and what the result would be.
  By all rights it should be simple and elegant code.
  Instead, it's a workmanlike hack. Emphasis is on 
  'works', however.

* subroutine 'moves' finds all possible descendants
  of a given board state by presenting consecutive 
  rotated board states to sub move

There are gains to be made by taking advantage of the
fourfold rotational symmetry here. But that would be
clever.

Also, this text representation (xsb format) is highly
compressible, and it might be worthwhile to store the
board states that way. This is a small puzzle (25 pos-
itions, 4 boxes - only 25*24*23*22*21/4*3*2*1 = 265650 
board states possible).

I get a solution (77 steps) in slighly over three min-
utes on this box (WinXP, single CPU, 3GHz). 


##############################################
#!/perl

use strict;
use warnings;
# use Devel::Size qw(total_size);

my $puzzle =&lt;&lt;'EOP';
  ####
  #  #
  #  ####
###$.$  #
#  .&lt; at &gt;.  #
#  $.$###
####  #
   #  #
   ####
EOP
solve($puzzle);


sub move
{
  my $puzzle = shift;
  my $pos = index($puzzle, '&lt; at &gt;');
  my $tar0 = my $tar1 = 0;
  
  if ($pos == -1)
  {
    $pos = index($puzzle, '+');
    $tar0 = 1;
  }
  
  my $next = substr($puzzle,$pos+1,1);
  
  if ($next eq ' ')
  {
    substr($puzzle, $pos+1, 1, '&lt; at &gt;');
    substr($puzzle, $pos,   1, $tar0 ? '.' : ' ');
    return $puzzle;    
  }
  elsif ($next eq '.')
  {
    substr($puzzle, $pos+1, 1, '+');
    substr($puzzle, $pos,   1, $tar0 ? '.' : ' ');
    return $puzzle;    
  }
  elsif ($next eq '#')
  {
    return undef;
  }
  
  # only boxes left
  $tar1 = 1 if $next eq '*';
  die "expecting a box (*\$), found '$next' in \n$puzzle\n" unless $next
eq '$' or $tar1;
  $next = substr($puzzle,$pos+2,1);
  
  if ($next eq ' ')
  {
    substr($puzzle, $pos+2, 1, '$');
    substr($puzzle, $pos+1, 1, $tar1 ? '+' : '&lt; at &gt;');
    substr($puzzle, $pos,   1, $tar0 ? '.' : ' ');
    return $puzzle;        
  }
  elsif ($next eq '.')
  {
    substr($puzzle, $pos+2, 1, '*');
    substr($puzzle, $pos+1, 1, $tar1 ? '+' : '&lt; at &gt;');
    substr($puzzle, $pos,   1, $tar0 ? '.' : ' ');
    return $puzzle;        
  }
  return undef;
}

sub rotate
{
  my $puzzle = shift;
  my &lt; at &gt;lines = split /\n/, $puzzle;

  my $maxl = 0;
  for (&lt; at &gt;lines)
  {
    my $l = length;
    $maxl = $l if $l &gt; $maxl;
  }
  
  my &lt; at &gt;newlines;
  for my $j (0..$#lines)
  {
    my $l = length $lines[$j];
    for my $i (0..$maxl-1)
    {
      my $char;
      if ($i &gt;= $l)
      {
        $char = ' ';
      }
      else
      {
        $char = substr($lines[$j],$i,1);
      }
      $newlines[$maxl-$i-1] .= $char;
    }
  }
  
  my $newpuzzle = (join "\n", &lt; at &gt;newlines) . "\n";
  return $newpuzzle;
}

sub moves
{
  my $puzzle = shift;
  my &lt; at &gt;moves;
  
  my $move = move($puzzle);
  push &lt; at &gt;moves, $move if defined $move;
  
  my $rot = rotate($puzzle);
  $move = move($rot);
  push &lt; at &gt;moves, rotate(rotate(rotate($move))) if defined $move;
  
  $rot = rotate($rot);
  $move = move($rot);
  push &lt; at &gt;moves, rotate(rotate($move)) if defined $move;
  
  $rot = rotate($rot);
  $move = move($rot);
  push &lt; at &gt;moves, rotate($move) if defined $move;
  
  return &lt; at &gt;moves;
}

sub is_solved
{
  my $puzzle = shift;
  
  return 0 if $puzzle =~ /\./;
  return 0 if $puzzle =~ /\+/;
  
  if ($puzzle =~ /\$/)
  {
    die "Apparently more boxes than targets in \n$puzzle\n";
  }
  
  return 1;
}


sub solve
{
  my $puzzle = shift;
  my %adjacency = ($puzzle =&gt; 'START');
  my &lt; at &gt;queue = ($puzzle);
  my $maxsize = 0;
  while (1)
  {
    my $state = shift &lt; at &gt;queue;
    for my $cand (moves($state))
    {
      next if exists $adjacency{$cand};
      $adjacency{$cand} = $state;
      if (is_solved($cand))
      {
        print_sol($cand, \%adjacency);
#my $size = total_size(\%adjacency) + total_size(\&lt; at &gt;queue);
#print "Tree contains $size bytes.\n";
        return;
      }
      push &lt; at &gt;queue, $cand;
    }
#    print join "\t", scalar(keys %adjacency), scalar &lt; at &gt;queue, "\n";
  }
  
  die "Unsolvable?";  
}

sub print_sol
{
  my $solution = shift;
  my $href = shift;
  
  my &lt; at &gt;steps;
  while ($solution ne 'START')
  {
    unshift &lt; at &gt;steps, $solution;
    $solution = $href-&gt;{$solution};
  }
  
  print join "---\n", &lt; at &gt;steps;
  print "---\n... and it took me only ".(time - $^T)." seconds!";
}


</description>
    <dc:creator>Jurgen Pletinckx</dc:creator>
    <dc:date>2008-01-04T14:44:15</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2634">
    <title>[QUIZ] Perl 'Hard' Quiz of the Whatever #2008-12-28 - Symmetric Sokoban</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2634</link>
    <description>IMPORTANT: Please do not post solutions, hints, or other spoilers
until at least 60 hours after the date of this message.  Thanks.

I was told the "What does this code do?" quizzes were not as good as a new 
programming task, so here's a more traditional QOTW that I came up with. In 
this quiz you'll try to solve the following Sokoban ( 
http://en.wikipedia.org/wiki/Sokoban ) puzzle using Perl:

{{{{{{{{{{{{{{
  ####
  #  #
  #  ####
###$.$  #
#  .&lt; at &gt;.  #
#  $.$###
####  #
   #  #
   ####
}}}}}}}}}}}}}}

If you're not familiar with the notation, then "#" are walls, "$" are the 
initial positions of the boxes to be moved, "." are the destinations and "&lt; at &gt;" 
is the initial position of the player.

This level is the Microban level No. 142 in the levels collection of KSokoban. 
You may want to solve it yourself before using Perl to do so. You can load it 
into a Sokoban clone by saving it into a file and then using the "load" 
command.

Good luck and happy new year!

Regards,

Shlomi Fish

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

I'm not an actor - I just play one on T.V.

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2007-12-28T11:31:02</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2633">
    <title>[QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2633</link>
    <description>Hi all!

Here's another "What does this code do?" Quiz. I obfuscated the names of the 
variable names to prevent hinting their purpose, but the code itself is 
not obfuscated. You have to guess what the "m2()" function does.

Good luck!

Please post spoilers as response to this post with "SPOILER".

Regards,

Shlomi Fish

&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
use strict;
use warnings;

sub m2
{
    my $c = shift;

    my $d = sub {
        my $e = shift;
        return +(ref($e) eq "ARRAY") ? $e-&gt;[1] : $e;
    };

    my $f = sub {
        my $e = shift;
        return +(ref($e) eq "ARRAY") ? $e-&gt;[0] : 1;
    };
    
    my $g;

    $g = sub {
        my ($h, $i, $j) = &lt; at &gt;_;

        if (($h == &lt; at &gt;$c) &amp;&amp; ($j == 0))
        {
            return ();
        }
        elsif ($j == 0)
        {
            return $g-&gt;($h+1, 
                $d-&gt;($c-&gt;[$h]),
                $f-&gt;($c-&gt;[$h])
            );
        }
        else
        {
            return ($i, $g-&gt;($h, $i, $j-1));
        }
    };

    return [ $g-&gt;(0, 0, 0) ];
}

1;

Regards,

Shlomi Fish

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

If it's not in my E-mail it doesn't happen. And if my E-mail is saying
one thing, and everything else says something else - E-mail will conquer.
    -- An Israeli Linuxer

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2007-10-28T17:06:31</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2632">
    <title>Re: [QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2632</link>
    <description>
WALKMETH is a P6 thing (described in Apocalypse 12
http://dev.perl.org/perl6/doc/design/apo/A12.html) that generates a
list of all methods that will respond to that invocation. My answer
wasn't quite correct - the implementation of CALLALL described in A12
is the closer analog to the code provided.

The "mostly complete" bit had to do with a few missing paranoia bits
in the P5 as well as the fact that CALLALL does a take as opposed to
executing the method right away (lazy vs. immediate). Personally, I
prefer lazy evaluation as the default, but that's not really possible
in P5 without breaking encapsulation or doing crazy things like
Contextual::Return and the like that completely blow your runtime
performance.

Rob

</description>
    <dc:creator>Rob Kinyon</dc:creator>
    <dc:date>2007-07-02T04:10:31</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2631">
    <title>Re: [QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2631</link>
    <description>





Is this the answer?

  DB&lt;54&gt; use Crypt::Rot13

  DB&lt;55&gt;  $c = Crypt::Rot13-&gt;new

  DB&lt;56&gt; $c-&gt;charge('xbmlnfui')

  DB&lt;57&gt; x $c-&gt;rot13(25)
0  'walkmeth'

But I don't know what 'walkmeth' is.

</description>
    <dc:creator>Greg Matheson</dc:creator>
    <dc:date>2007-07-01T15:08:35</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2630">
    <title>Re: SPOILER: 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2630</link>
    <description>
That's right.


Well, the thing is that it is very unlikely, I will create a different glob 
with the name "$mn", which will be different than a subroutine. But point 
taken.


If it returns anything except an array ref, then I'd like to know about it. So 
I can assume that the contract of the function will be that, and that the 
functions of those names will be dedicated for them.

As for the origin: this code is originally based on a concept that Damian 
Conway introduced in Class::Std ( http://search.cpan.org/dist/Class-Std/ ) - 
the "CUMULATIVE" methods. I believe I read about it in his Perl Best 
Practices book.

I later implemented this feature in a somewhat different way in Test::Run 
(without making use of any of Damian's original Class::Std code), and this 
code here is based on the Test::Run code.

Regards,

Shlomi Fish

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

If it's not in my E-mail it doesn't happen. And if my E-mail is saying
one thing, and everything else says something else - E-mail will conquer.
    -- An Israeli Linuxer

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2007-07-01T12:49:11</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2629">
    <title>Re: [QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2629</link>
    <description>
What is XBMLNFUI?

I don't get it.

Regards,

Shlomi Fish


---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

If it's not in my E-mail it doesn't happen. And if my E-mail is saying
one thing, and everything else says something else - E-mail will conquer.
    -- An Israeli Linuxer

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2007-07-01T12:37:09</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2628">
    <title>Re: SPOILER: 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2628</link>
    <description>
Yeah, yeah, yeah, exercise for the reader, etc, etc :-)


See, the answer was within you all the time ;-)
</description>
    <dc:creator>Peter Scott</dc:creator>
    <dc:date>2007-06-27T21:29:26</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2627">
    <title>Re: SPOILER: 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2627</link>
    <description>
Good catch, but of course you wouldn't want to actually do that...
what if $m-&gt;($self) is very expensive, or has side effects? There's
also a race condition: how can you be sure that an arrayref return
from the first invocation of $m-&gt;($self) guarantees an arrayref return
(whether or not the same data) from the second invocation?

  my $result = $m-&gt;($self);
  push &lt; at &gt;r, &lt; at &gt;$result if (ref $result eq 'ARRAY');

--
Ron Isaacson
Morgan Stanley
ron.isaacson-/PgpppG8B+R7qynMiXIxWgC/G2K4zDHf&lt; at &gt;public.gmane.org / (212) 762-1950

</description>
    <dc:creator>Ron Isaacson</dc:creator>
    <dc:date>2007-06-27T20:42:59</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2626">
    <title>Re: [QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2626</link>
    <description>
Nice. An mostly complete implementation of XBMLNFUI*  in P5.

Rob

* 13x simpler than rot13 ...

</description>
    <dc:creator>Rob Kinyon</dc:creator>
    <dc:date>2007-06-27T20:22:14</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2625">
    <title>SPOILER: 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2625</link>
    <description>
$object_or_class-&gt;z( { mn =&gt; 'funcname' } ) will return a reference to 
an array of all the list-context results of calling the 'funcname' 
method in the class of $object_or_class and its ancestors in parental 
order, assuming that each such method returns an arrayref, and that the 
above code is present in or inherited by the class of $object_or_class.

However, it checks to see only if the glob 'funcname' is defined, not 
whether there is a subroutine of that name.  It will generate a warning 
if another slot in the glob is used instead.  I would rewrite the **** 
line above as

         my $m = *{$i . "::$mn"}{CODE};

and to be on the safe side, rewrite the %%%% line as

             push &lt; at &gt;r, &lt; at &gt;{ ref $m-&gt;($self) eq 'ARRAY' ? $m-&gt;($self) : [] };

</description>
    <dc:creator>Peter Scott</dc:creator>
    <dc:date>2007-06-27T20:14:06</dc:date>
  </item>
  <item rdf:about="http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2624">
    <title>[QUIZ] Perl 'What does this code do?' Quiz</title>
    <link>http://permalink.gmane.org/gmane.comp.lang.perl.qotw.discuss/2624</link>
    <description>Hi all!

Here's another "What does this code do?" Quiz. I obfuscated the names of the 
variable names to provide hinting on their purpose, but the code itself is 
not obfuscated. You have to guess what the "z()" function does.

Good luck!

Please post spoilers as response to this post with "SPOILER".

Regards,

Shlomi Fish

&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;
use strict;
use warnings;

use List::MoreUtils (qw(uniq));

our %_t = ();

sub t
{
    my $c = shift;

    if (exists($_t{$c}))
    {
        return $_t{$c};
    }

    no strict 'refs';

    my &lt; at &gt;h = $c;
    my &lt; at &gt;d = &lt; at &gt;{$c. '::ISA'};

    while (my $p = shift(&lt; at &gt;d))
    {
        push &lt; at &gt;h, $p;
        push &lt; at &gt;d, &lt; at &gt;{$p. '::ISA'};
    }

    my &lt; at &gt;u = uniq(&lt; at &gt;h);

    return $_t{$c} =
        [
            sort
            {
                  $a-&gt;isa($b) ? -1
                : $b-&gt;isa($a) ? +1
                :               0 
            }
            &lt; at &gt;u
        ];
}

sub z
{
    my ($self, $args) = &lt; at &gt;_;

    my $mn = $args-&gt;{mn};

    my $c = ((ref($self) eq "") ? $self : ref($self));

    my $h= t($c);

    my &lt; at &gt;r;
    foreach my $i (&lt; at &gt;$h)
    {
        no strict 'refs';
        my $m = ${$i . "::"}{$mn};
        if (defined($m))
        {
            push &lt; at &gt;r, &lt; at &gt;{$m-&gt;($self)};
        }
    }
    return \&lt; at &gt;r;
}

Regards,

Shlomi Fish

---------------------------------------------------------------------
Shlomi Fish      shlomif-ik1l9ssToec+JF/nGntIXQ&lt; at &gt;public.gmane.org
Homepage:        http://www.shlomifish.org/

If it's not in my E-mail it doesn't happen. And if my E-mail is saying
one thing, and everything else says something else - E-mail will conquer.
    -- An Israeli Linuxer

</description>
    <dc:creator>Shlomi Fish</dc:creator>
    <dc:date>2007-06-27T19:05:53</dc:date>
  </item>
  <textinput about="http://search.gmane.org/?group=$group=gmane.comp.lang.perl.qotw.discuss">
    <title>Search Engine</title>
    <description>Search the mailing list at Gmane</description>
    <name>query</name>
    <link>http://search.gmane.org/?group=$group=gmane.comp.lang.perl.qotw.discuss</link>
  </textinput>
</rdf:RDF>
