[SPOILER] Solution to Perl Quiz of the Week #23

Xavier Noria <[email protected]>
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
On Sep 1, 2004, at 18:50, Mark Jason Dominus wrote:

> Write a program, 'parens', which gets a command line argument, n',
> which is an integer.  The program should print all the
> properly-balanced strings of parentheses of length 2n.

I wrote a couple of solutions, one recursive, and one iterative.

To easily keep track of the balance at any given point of the search 
the strings are represented as arrays of {-1, 1}, and converted to a 
sequence of parens just for output.

Recursive
¨¨¨¨¨¨¨¨¨
The recursive solution goes like this: There are two kinds of 
properly-balanced strings of length 2n, the ones starting with "()" and 
the ones starting with "((". In the former case, the rest of the string 
is a properly-balanced string of length 2(n - 1), which maps to 
recursion directly. Whereas in the latter, the rest of the string has 
length 2(n - 1) and a balance of -2. We generate those unbalanced 
strings from balanced ones.

The performance gets worse with n both in terms of time and memory 
usage.

Iterative
¨¨¨¨¨¨¨¨¨
Since in the past quiz I "unrolled" a recursion, emulating it with a 
stack and a loop, I chose another approach for this one. Since for a 
known n the generation of solutions via nested loops is 
straightforward, the script generates them and evals the code.

As soon as we know we don't need to look further we halt a loop 
(negative balance, or too much opened parens). This is the generated 
code for n = 3 (filtered through perltidy -io):

my $bal = 0;
my @paren = (undef, '(', ')');
for my $i1 (1, -1) {
     $bal += $i1;
     next if $bal < 0
       ;
     for my $i2 (1, -1) {
         $bal += $i2;
         next if $bal < 0
           ;
         for my $i3 (1, -1) {
             $bal += $i3;
             next if $bal < 0
               ;
             for my $i4 (1, -1) {
                 $bal += $i4;
                 next if $bal < 0
                   || $bal > 2;
                 for my $i5 (1, -1) {
                     $bal += $i5;
                     next if $bal < 0
                       || $bal > 1;
                     for my $i6 (1, -1) {
                         $bal += $i6;
                         next if $bal;
                         print $paren[$i1];
                         print $paren[$i2];
                         print $paren[$i3];
                         print $paren[$i4];
                         print $paren[$i5];
                         print $paren[$i6];
                         print "\n";
                     } continue {
                         $bal -= $i6;
                     }
                 } continue {
                     $bal -= $i5;
                 }
             } continue {
                 $bal -= $i4;
             }
         } continue {
             $bal -= $i3;
         }
     } continue {
         $bal -= $i2;
     }
} continue {
     $bal -= $i1;
}

This version is much faster, and the memory usage is under control.

-- fxn




Recursive
¨¨¨¨¨¨¨¨¨
#!/usr/bin/perl

use strict;
use warnings;

my $n = shift;
defined $n and $n > 0 or die <<USAGE;
Usage: $0 positive_integer
USAGE

for my $b (@{balanced($n)}) {
     my @in_chars = map { $_ > 0 ? '(' : ')' } @$b;
     print join '', @in_chars, "\n";
}

sub balanced {
     my $n = shift;
     return [[1, -1]] if $n == 1;
     my @bs = ();
     for $b (@{balanced($n - 1)}) {
         push @bs, [1, -1, @$b];
         my $count = 0;
         my $offset = 0;
         for my $p (@$b) {
             ++$offset;
             $count += $p;
             if ($count <= 0) {
                 my @new = @$b;
                 splice @new, $offset, 0, -1;
                 push @bs, [1, @new];
             }
         }
     }
     return \@bs;
}



Iterative
¨¨¨¨¨¨¨¨¨
#!/usr/bin/perl

use strict;
use warnings;

my $n = shift;
defined $n and $n > 0 or die <<USAGE;
Usage: $0 positive_integer
USAGE

my $t = 2*$n;

# The code that prints a solution.
my $print = '';
$print .= "print \$paren[\$i$_];\n" for 1..$t;
$print .= 'print "\n";';

my $m = $t;
my @code = (<<DEEPEST_LOOP);
for my \$i$m (1, -1) {
\$bal += \$i$m;
next if \$bal;
$print
} continue {
\$bal -= \$i$m;
}
DEEPEST_LOOP

for $m (reverse 1..($t - 1)) {
     my $for = <<FOR;
for my \$i$m (1, -1) {
\$bal += \$i$m;
next if \$bal < 0
FOR
     $for .= " || \$bal > @{[$t - $m]}" if $m > $t/2;
     $for .= ";\n";
     unshift @code, $for;
     push @code, <<CONTINUE;
} continue {
\$bal -= \$i$m;
}
CONTINUE
}

unshift @code, <<TOP;
my \$bal = 0;
my \@paren = (undef, '(', ')');
TOP

eval join '', @code;
#print @code;
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.