[SPOILER] solution to QOW #23

Leo Cacciari <[email protected]>
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
On Wed, Sep 01, 2004 at 12:50:14PM -0400, Mark Jason Dominus scripsit:
> 
> 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.  For example,
> given the argument '3', the program should print these five lines:
> 
>         ((()))
>         (()())
>         (())()
>         ()(())
>         ()()()
> 
> in some order.  (The order is not important.)  
> 

Here is my solution. It is based on fairly well known results of 
contect-free language's theory which I'm goingo to briefly discuss.

First of all, the laguage of balaced parentheses is generated by the
following context-free grammar:

(1)  S => \epsilon | (S)S

where \epsilon is the empty word. (1) can be explained by saying:

   A string of balanced parentheses is either empy or it starts by an
   open parenthese, followed by a balanced string of parentheses, followed by 
   the closing parenthese correspondig to the first open one, followed 
   again by a string of balanced parentheses.

Remark that this decomposition is unique, this is important in assuring that
the program below generate each given string only one time.

In the program we start with the string 'S' then we repeatedly apply the
rules in (1) in order to generate all proberly-balanced parentheses
string. We discard a string when one of the following conditions arises:

   (a) there are exactly n '(' in the string 
   (b) there are no more 'S' in the string and the length is < 2*n
   (c) there are more than n+1 'S' in the string
   
In case (a) we replace all 'S' by \epsilon and print the string. Strings in
case (b) must obviously be discarded. To prove that
strings in case (c) can be dropped, simply remarks that a string derived by
applications of (1) starting from S satisfy the following:

(2)               # of 'S' <= # of '(' - 1

Then a case (c) string has more than n '(' and can't possibly lead to a
string with exactly n '('.

Here is the program

#!/usr/bin/perl -wl
use strict;

my $n = shift;

my @stack = qw(S);


while (@stack) {
  my $str = pop @stack;
  my $nt = ($str =~ tr!S!S!);
  my $par = (length($str) - $nt)/2;
  
  if ($par == $n) {
    $str =~ s!S!!g;
    print $str;
  }
  elsif ($nt <= $n) {
    if ($nt > 1) {
      (my $new = $str) =~ s!S!!;
      push @stack,$new;
    }
    $str =~ s!S!(S)S!;
    push @stack,$str;
  }
}

__END__

As I haven't had access in the last days to a proper machine, but only to
my 860MHz Pentium III laptop, I'll say nothing about performances, unless
it is to introduce the 'optimized' version below. In this version we avoid
some string copying by pushing in the stack the _references_ to strings
instead of the strings themselves. Moreover, we also push on the stack the
number of 'S' of the string as well as the number of open parenthese, so
that we do not have to calculate them each time. 

Here is this optimized version which, on my laptop, run about 25% faster
than the not optimized one

#!/usr/bin/perl -wl

use strict;

my $n = shift;


my $str = "S";
my @stack = (0,1,\$str);


while (@stack) {
  my $str = pop @stack;
  my $nt = pop @stack;
  my $par = pop @stack;
  
  if ($par == $n) {
    $$str =~ s!S!!g;
    print $$str;
  }
  elsif ($nt <= $n) {
    if ($nt > 1) {
      (my $new = $$str) =~ s!S!!;
      push @stack,$par,$nt-1,\$new;
    }
    $$str =~ s!S!(S)S!;
    push @stack,$par+1,$nt+1,$str;
  }
}


-- 
Leo"TheHobbit" Cacciari
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.