in reply to Full justification - 3 methods

I decided to write this in C to see how easy/hard it would be. It turned out to not be that hard but made me realize that the methods could be simplified by not having them work directly on the strings (lines). Instead, I made an "expansion map," held in an array.

Each index in the array corresponds to the respective space in the string (ie, index 0 is the first space, etc) and holds a number that tells us how much we should expand that space. So if, for example, index 5 held the number 2, we would replace the sixth space in the string with two spaces.

This simplifies the methods because we no longer have to account for a string of ever expanding length, plus we don't end up re-copying half of the string repeatedly.

The basic idea behind the methods remain the same. Using a "space table" does introduce one side-effect, however. The middle-out method bases it's starting position on the middle of the table instead of the middle of the string. In almost all instances, this will not create a noticable difference, though.

If you're interested in seeing the C version and how the code compares to the Perl (it's just about twice as long as the Perl; mostly due to setup for things like getopt_long and emulating perl's -n option, as well as not having an easy way to count or expand the spaces), it's available at http://www.exnoctum.f2s.com/just_c.c. I'm interested in what people think of both versions. =)

This version also fixes a few minor bugs related to balancing of spaces in the middle-out method, as well as one or two minor issues with the user options (ie, I added short-forms of --condense and --nocondense).

Anyway, here's the new version:


#!/usr/bin/perl -wn BEGIN { $LINE_WIDTH = 75; $METHOD = \&middle_out; $CONDENSE = 1; use Getopt::Long; Getopt::Long::Configure('bundling'); GetOptions( 'c|condense' => sub { $CONDENSE = 1 }, 'C|nocondense' => sub { $CONDENSE = 1 }, 'm|middle' => sub { $METHOD = \&middle_out }, 'l|left' => sub { $METHOD = \&left_to_right }, 'r|right' => sub { $METHOD = \&right_to_left }, 'w|width=i' => sub { $LINE_WIDTH = $_[1]; die "$0: invalid WIDTH (must be between 1 and 255)\n" if($LINE_WIDTH < 1 or $LINE_WIDTH > 255); }, 'help' => sub { my $self = $0; $self =~ s/^.*\///; print << "USAGE"; Usage: $self [OPTION]... [FILE]... Fully justifies a stream of text using one of three different methods. -c, --condense condense multiple spaces before justifying (de +fault) -C, --nocondense -m, --middle use the middle-out method (default) -l, --left use the left-to-right method -r, --right use the right-to-left method -w, --width=WIDTH set the line-width to WIDTH (default is 75) --help display this help and exit The different methods describe where it starts expanding spaces to get lines to be the appropriate width. For example, middle-out starts in the middle of the line, expanding spaces in both directions. The middle-out method usually looks best, although right-to-left is some- times better. USAGE exit(0); }, ); } if($CONDENSE and !/^ /) { s/ +$//g; s/(?<!\.) {2,}/ /g; } if(defined $last_line) { if($last_line !~ /^( |$)/ and $_ !~ /^$/) { @space_table = (1) x $last_line =~ s/(?<!\.) / /g; $METHOD->(\@space_table, length($last_line)); $cntr = 0; $last_line =~ s/(?<!\.) /' 'x$space_table[$cntr++]/ge; } print $last_line; } $last_line = $_; END { print $last_line if defined $last_line } #*************************************************# #******************* METHODS *******************# #*************************************************# sub middle_out { my $tbl_ref = shift; my $tbl_len = @$tbl_ref; my $str_len = shift; return unless($tbl_len); use integer; my ($pr, $pf); # pf and pr can't start at the same point, otherwise # we'll get an extra space added there (as they both # try to expand the same space). We need to adjust # them differently according to the even-ness of # tbl_len to get things properly balanced. if($tbl_len & 1) { $pr = $tbl_len / 2; $pf = $pr + 1; } else { $pf = $tbl_len / 2; $pr = $pf-1; } while($str_len <= $LINE_WIDTH) { # reset $pr and $pf if($pr == -1 and $pf == $tbl_len) { if($tbl_len & 1) { $pr = $tbl_len / 2; $pf = $pr + 1; } else { $pf = $tbl_len / 2; $pr = $pf-1; } } # back unless($pr == -1) { $tbl_ref->[$pr--]++; last if(++$str_len > $LINE_WIDTH); } # forward unless($pf == $tbl_len) { $tbl_ref->[$pf++]++; last if(++$str_len > $LINE_WIDTH); } } } sub left_to_right { my $tbl_ref = shift; my $tbl_len = @$tbl_ref; my $str_len = shift; return unless($tbl_len); my $pf = 0; while($str_len <= $LINE_WIDTH) { $pf = 0 if($pf == $tbl_len); $tbl_ref->[$pf++]++; ++$str_len; } } sub right_to_left { my $tbl_ref = shift; my $tbl_len = @$tbl_ref; my $str_len = shift; return unless($tbl_len); my $pr = $tbl_len-1; while($str_len <= $LINE_WIDTH) { $pr = $tbl_len-1 if($pr == -1); $tbl_ref->[$pr--]++; ++$str_len; } }

bbfu
Seasons don't fear The Reaper.
Nor do the wind, the sun, and the rain.
We can be like they are.