Formatting Text, Again

October 10, 2014

Today’s exercise is a continuation of the previous exercise, which filled text to the maximum column width. Today’s exercise is to add justification to the previous exercise, so that extra space at the end of line of text is distributed to the spaces between the words on the line, making each line end at the right margin.

Your task is to write a program that fills and justifies text. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

2 Responses to “Formatting Text, Again”

  1. matthew said

    I thought that might be the next stage. Here’s a version of my C++ program – it attempts to spread out the padding space evenly through the line (and goes in alternating directions – perhaps we should use the Morse-Thue sequence for this to ensure there aren’t any other regularities).

    It’s a bit long-winded, but here it is:

    #include <stdlib.h>
    #include <iostream>
    #include <sstream>
    #include <string>
    
    // spaces is the number of spaces already in the line
    // n is the desired length of the line
    void padline(std::string &line, int n, int spaces, int lineno)
    {
      int lsize = line.size();
      if (lsize < n && spaces > 0) {
        int needed = n-lsize;
        line.resize(n); // Make room
        int t = 0; // Sort of a token bucket
        int nleft = spaces;
        int i = lsize-1; // Indexes for string copying
        int j = n-1;
        while (nleft > 0) {
          line[j] = line[i]; // Move a character up
          if (line[i] == ' ') {
            nleft--;
            int scount = 0; // Number of spaces to insert
            if (lineno%2) {
              t += needed; // Add some more tokens
              while (t >= spaces) { t -= spaces; scount++; }
            } else {
              t -= needed; // Reverse of above
              while (t < 0) { t += spaces; scount++; }
            }
            for (int i = 0; i < scount; i++) { line[--j] = ' '; }
          }
          --j; --i;
        }
      }
    }
    
    int main(int argc, char *argv[])
    {
       std::string line;
       std::string outline;
       int current = 0;
       enum { START, NORMAL, PARA } state = START;
       int max = atoi(argv[1]);
       int spaces = 0;
       int lineno = 0;
       while(std::getline(std::cin, line)) {
          std::istringstream s(line);
          std::string word;
          bool empty = true;
          while(s >> word) {
             int wlen = word.size();
             switch (state) {
             case START:
                state = NORMAL;
                break;
             case NORMAL:
                if (current + wlen > max) {
                   current = 0;
                   padline(outline,max,spaces,lineno);
                   lineno++;
                   std::cout << outline << "\n";
                   outline = "";
                   spaces = 0;
                } else {
                   outline += " ";
                   spaces++;
                }
                break;
             case PARA:
                current = 0;
                lineno++;
                std::cout << outline << "\n\n";
                outline = "";
                spaces = 0;
                state = NORMAL;
                break;
             }
             current += wlen + 1;
             outline += word;
             empty = false;
          }
          if (empty && state == NORMAL) state = PARA;
       }
       if (state != START) std::cout << outline << "\n";
    }
    
  2. use strict;
    use warnings;
    
    my $width = shift @ARGV || 60;
    $/ = undef;
    
    print f($_) foreach split m{\n\s*\n}mxs, <>;
    
    sub pad {
      my ( $len, $ll, @line ) = @_;
      my $s   = (@line-1)/2;
      my $sta = ($len - $ll)/$s+0.0001;
      my $n=0;
      foreach(0..($s-1)) {
        $n += $sta;
        $line[2*$_] .= q( )x($n+.5);
        $n -= int ($n+.5);
      }
      return @line;
    }
    
    sub f {
      my ($ll,@out,@line) = (-1);
      foreach( split m{\s+}, shift ) {
        if( $ll + 1 + length $_ > $width ) {
          push @out, pad( $width, $ll, @line ), "\n";
          @line=();
          $ll = -1;
        } else {
          push @line, q( ) if @line;
        }
        $ll+= 1 + length $_;
        push @line,$_;
      }
      push @out, @line,"\n\n";
      return join q(), @out;
    }
    

Leave a comment