Perl Quiz of the Week #23 [SPOILER]

Bill Tucker <[email protected]>
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <a06100500bd5de90b045a@[192.168.1.101]>
I first built a looping solution, rather than a recursive one.

This first solution builds an array of strings, starting with an 
empty string. At each step it does the following (L is the number of 
"("s already in the string, R is the number of ")"s, and N is the 
length of the string, i.e., 2*n):

    - If (L-R) < (N-(L+R)), it's OK to add a "(". (And yes, I know 
that expression is equivalent to 2*L < N, but I'm only keeping track 
of L-R.)
    - If (L-R) > 0, it's OK to add a ")".

The size of the arrays grows rather quickly! On my 500 MHz G3 iBook, 
I get the following run times:
n = 00 took       0.07 seconds
n = 01 took       0.07 seconds
n = 02 took       0.17 seconds
n = 03 took       0.07 seconds
n = 04 took       0.12 seconds
n = 05 took       0.12 seconds
n = 06 took       0.08 seconds
n = 07 took       0.14 seconds
n = 08 took       0.27 seconds
n = 09 took       0.60 seconds
n = 10 took       1.71 seconds
n = 11 took       5.53 seconds
n = 12 took      19.42 seconds
n = 13 took      63.43 seconds
n = 14 took     264.16 seconds

Those times were done with the print commented out.

Something bugged me about this, though. It seemed to me that a 
solution could be based on a binary tree traversal, which used the 
formulas above to prune sections of the tree known not to represent 
valid strings. I thought about it for a while, and came up with the 
second solution below. The really cool thing is that since every node 
is identical, I need not even build the tree. Thus, it winds up being 
a sort of smart binary counter. It doesn't run much faster than the 
first solution:
n = 00 took       0.08 seconds
n = 01 took       0.06 seconds
n = 02 took       0.18 seconds
n = 03 took       0.06 seconds
n = 04 took       0.11 seconds
n = 05 took       0.10 seconds
n = 06 took       0.09 seconds
n = 07 took       0.15 seconds
n = 08 took       0.31 seconds
n = 09 took       0.57 seconds
n = 10 took       1.57 seconds
n = 11 took       6.04 seconds
n = 12 took      19.62 seconds
n = 13 took      64.13 seconds
n = 14 took     228.41 seconds

It does have two major virtues, however - it uses very little memory 
and starts printing out the strings immediately, even for large n. 
(Well, OK, for n=1000000, it takes about 30 seconds to print the 
first string. But with the first solution, the machine would crumble 
into dust before it started printing.)

Bill

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

#!/usr/bin/perl -w
#
#  First solution - this uses lots of memory for large n

use strict;

my $n;                      # 2n is the length of the strings to produce
my ($strings, $newStrings); # Refs to arrays containing the output strings
my ($sums,    $newSums   ); # For each string, # of "("s - # of ")"s

$n = shift;                 # Get $n from the command line
($n =~ /^\d+$/) or die "Please specify a numeric length!\n";

$strings = [""];
$sums    = [0];

foreach (0..2*$n-1)
     {
     # Init the new strings and sums arrays
     $newStrings = [];
     $newSums    = [];

     foreach (0..scalar(@$strings)-1)
         {
         # Check to see if we can add a ( to the string
         if ($$sums[$_] + length($$strings[$_]) < 2*$n)
             {
             push (@$newStrings, $$strings[$_]."(");
             push (@$newSums,    $$sums[$_]+1);
             }
         # Check to see if we can add a ) to the string
         if ($$sums[$_] > 0)
             {
             push (@$newStrings, $$strings[$_].")");
             push (@$newSums,    $$sums[$_]-1);
             }
         }
     $strings = $newStrings;
     $sums    = $newSums;
     }
foreach (@$strings)
     {
     print "$_\n";
     }
exit;


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

#!/usr/bin/perl -w
#
# Second solution - uses very little memory

use strict;

my $n;                        # 2n is the length of the strings to produce
my (@triedLeft, @triedRight); # For each 'node', have we tried to add ( and )?
my (@curString);              # An array of the chars in the string
my ($sum);                    # For current string, # of '(' - # of ')'
my ($curPos);                 # Where in the string are we?

$n = shift;                   # Get $n from the command line
defined($n) and ($n =~ /^\d+$/) or die "Please specify a numeric length!\n";
$n or exit;

foreach (0..2*$n-1)
     {
     $triedLeft[$_]  = 0;
     $triedRight[$_] = 0;
     }

$curPos = 0;
$sum    = 0;

while (1)
     {
     # Check to see if we can add a ( to the string
     if (!$triedLeft[$curPos] and ($sum + $curPos) < 2*$n)
         {
         push (@curString,"(");
         $triedLeft[$curPos] = 1;
         $curPos++;
         $sum++;
         }
     # Check to see if we can add a ) to the string
     elsif (!$triedRight[$curPos] and $sum > 0)
         {
         push (@curString,")");
         $triedRight[$curPos] = 1;
         $curPos++;
         $sum--;
         }
     else # This node is done; reset it and pop up to its parent
         {
         $triedLeft[$curPos]  = 0;
         $triedRight[$curPos] = 0;
         $curPos--;
         (pop(@curString) eq "(") ? ($sum--) : ($sum++);
         }
     $curPos or last; # Once we've popped back to the top, we're done
     ($curPos > 2*$n-1) and print join('',@curString)."\n";
     }
exit;

-- 
+--------------------------------+-------------------------------------------+
| Bill Tucker                    | My life has a superb cast                 |
| [email protected] | but I can't figure out the plot.          |
| +855 (012) 654236              |            - Ashleigh Brilliant           |
+--------------------------------+-------------------------------------------+
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.