Align columns

From Rosetta Code
Revision as of 16:10, 6 August 2009 by rosettacode>Paddy3118 (→‎{{header|R}}: Incorrect. Alignment in the rightmost columns is wrong.)
Task
Align columns
You are encouraged to solve this task according to the task description, using any language you may know.

Given a text file of many lines, where fields within a line are delineated by a single 'dollar' character, write a program that aligns each column of fields by ensuring that words in each column are separated by at least one space. Further, allow for each word in a column to be either left justified, right justified, or center justified within its column.


Use the following text to test your programs:

Given$a$text$file$of$many$lines,$where$fields$within$a$line$
are$delineated$by$a$single$'dollar'$character,$write$a$program
that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$
column$are$separated$by$at$least$one$space.
Further,$allow$for$each$word$in$a$column$to$be$either$left$
justified,$right$justified,$or$center$justified$within$its$column.

Note that:

  1. The example input texts lines may, or may not, have trailing dollar characters.
  2. All columns should share the same alignment.
  3. Consecutive space characters produced adjacent to the end of lines are insignificant for the purposes of the task.
  4. Output text will be viewed in a mono-spaced font.

Ada

<lang ada> with Ada.Characters.Latin_1; use Ada.Characters.Latin_1; with Ada.Text_IO; use Ada.Text_IO; with Strings_Edit; use Strings_Edit;

procedure Column_Aligner is

  Text : constant String :=
     "Given$a$text$file$of$many$lines,$where$fields$within$a$line$" & NUL &
     "are$delineated$by$a$single$'dollar'$character,$write$a$program" & NUL &
     "that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$" & NUL &
     "column$are$separated$by$at$least$one$space." & NUL &
     "Further,$allow$for$each$word$in$a$column$to$be$either$left$" & NUL &
     "justified,$right$justified,$or$center$justified$within$its$column." & NUL;
  File    : File_Type;
  Width   : array (1..1_000) of Natural := (others => 0);
  Line    : String (1..200);
  Column  : Positive := 1;
  Start   : Positive := 1;
  Pointer : Positive;

begin

  Create (File, Out_File, "columned.txt");
     -- Determining the widths of columns
  for I in Text'Range loop
     case Text (I) is
        when '$' | NUL =>
           Width (Column) := Natural'Max (Width (Column), I - Start + 1);
           Start  := I + 1;
           if Text (I) = NUL then
              Column := 1;
           else
              Column := Column + 1;
           end if;
        when others =>
           null;
     end case;
  end loop;
     -- Formatting
  for Align in Alignment loop
     Column  := 1;
     Start   := 1;
     Pointer := 1;
     for I in Text'Range loop
        case Text (I) is
           when '$' | NUL =>
              Put -- Formatted output of a word
              (  Destination => Line,
                 Pointer     => Pointer,
                 Value       => Text (Start..I - 1),
                 Field       => Width (Column),
                 Justify     => Align
              );
              Start  := I + 1;
              if Text (I) = NUL then
                 Put_Line (File, Line (1..Pointer - 1));
                 Pointer := 1;
                 Column := 1;
              else
                 Column := Column + 1;
              end if;
           when others =>
              null;
        end case;
     end loop;
  end loop;
  Close (File);

end Column_Aligner; </lang> Formatted file sample:

Given      a          text       file   of     many      lines,     where    fields  within  a      line  
are        delineated by         a      single 'dollar'  character, write    a       program 
that       aligns     each       column of     fields    by         ensuring that    words   in     each  
column     are        separated  by     at     least     one        space.   
Further,   allow      for        each   word   in        a          column   to      be      either left  
justified, right      justified, or     center justified within     its      column. 
      Given          a       text   file     of      many     lines,    where  fields  within      a line 
        are delineated         by      a single  'dollar' character,    write       a program
       that     aligns       each column     of    fields         by ensuring    that   words     in each 
     column        are  separated     by     at     least        one   space.
   Further,      allow        for   each   word        in          a   column      to      be either left 
 justified,      right justified,     or center justified     within      its column.
   Given        a        text     file    of      many     lines,     where   fields  within    a   line  
    are    delineated     by        a   single  'dollar' character,   write     a    program 
   that      aligns      each    column   of     fields      by     ensuring   that   words    in   each  
  column       are     separated   by     at     least       one     space.  
 Further,     allow       for     each   word      in         a      column     to      be   either left  
justified,    right   justified,   or   center justified   within      its   column. 

ALGOL 68

<lang algol> STRING nl = REPR 10; STRING text in list := "Given$a$text$file$of$many$lines,$where$fields$within$a$line$"+nl+

 "are$delineated$by$a$single$'dollar'$character,$write$a$program"+nl+
 "that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$"+nl+
 "column$are$separated$by$at$least$one$space."+nl+
 "Further,$allow$for$each$word$in$a$column$to$be$either$left$"+nl+
 "justified,$right$justified,$or$center$justified$within$its$column.";

MODE PAGE = FLEX[0,0]STRING; PAGE page;

PROC flex page = (PAGE in page, INT row, col)PAGE:(

 HEAP FLEX[row, col]STRING out page;
 out page[:1 UPB in page, :2 UPB in page] := in page;
 FOR r TO row DO
   FOR c FROM 2 UPB in page + 1 TO col DO out page[r,c]:="" OD
 OD;
 FOR r FROM 1 UPB in page + 1 TO row DO
   FOR c FROM 1 TO col DO out page[r,c]:="" OD
 OD;
 out page

);

FILE text in file; associate(text in file, text in list); make term(text in file, "$");

on physical file end(text in file, (REF FILE skip)BOOL: stop iteration); on logical file end(text in file, (REF FILE skip)BOOL: stop iteration); FOR row DO

 on line end(text in file, (REF FILE skip)BOOL: stop iteration);
 FOR col DO
   STRING tok;
   getf(text in file, ($gx$,tok));
   IF row > 1 UPB page THEN page := flex page(page, row, 2 UPB page) FI; 
   IF col > 2 UPB page THEN page := flex page(page, 1 UPB page, col) FI; 
   page[row,col]:=tok
 OD;
 stop iteration: 
   SKIP

OD; stop iteration:

 SKIP;

BEGIN

 PROC aligner = (PAGE in page, PROC (STRING,INT)STRING aligner)VOID:(
   PAGE page := in page;
   [2 UPB page]INT max width;
   FOR col TO 2 UPB page DO
     INT max len:=0; FOR row TO UPB page DO IF UPB page[row,col]>max len THEN max len:=UPB page[row,col] FI OD;
     FOR row TO UPB page DO page[row,col] := aligner(page[row,col], maxlen) OD
   OD;
   printf(($n(UPB page)(n(2 UPB page -1)(gx)gl)$,page))
 );
 PROC left = (STRING in, INT len)STRING: in + " "*(len - UPB in),
      right = (STRING in, INT len)STRING: " "*(len - UPB in) + in,
      centre = (STRING in, INT len)STRING: ( INT pad=len-UPB in;  pad%2*" "+ in + (pad-pad%2)*" " );
 
 []STRUCT(STRING name, PROC(STRING,INT)STRING align) aligners = (("Left",left), ("Left",right), ("Centre",centre));
 
 FOR index TO UPB aligners DO 
   print((new line, "# ",name OF aligners[index]," Column-aligned output:",new line));
   aligner(page, align OF aligners[index]) 
 OD

END</lang>

AWK

<lang awk>BEGIN {

 FS="$"
 lcounter = 1
 maxfield = 0
 # justistification; pick up one
 #justify = "left"
 justify = "center"
 #justify = "right"

} {

 if ( NF > maxfield ) maxfield = NF;
 for(i=1; i <= NF; i++) {
   line[lcounter,i] = $i
   if ( longest[i] == "" ) longest[i] = 0;
   if ( length($i) > longest[i] ) longest[i] = length($i);
 }
 lcounter++

} END {

 just = (justify == "left") ? "-" : ""
 for(i=1; i <= NR; i++) {
   for(j=1; j <= maxfield; j++) {
     if ( justify != "center" ) {

template = "%" just longest[j] "s "

     } else {

v = int((longest[j] - length(line[i,j]))/2) rt = "%" v+1 "s%%-%ds" template = sprintf(rt, "", longest[j] - v)

     }
     printf(template, line[i,j])
   }
   print ""
 }

}</lang>

C

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>

struct linelement {

 char **wordlist;
 int longest;
 struct linelement *next;

};

typedef struct linelement line_t;

char *trim(char *s, int *len) {

 char *b;
 int l;
 while( (*s == ' ') || (*s == '\t') ) s++;
 b = s; 
 l = strlen(b);
 s += l; s--;
 while( (*s == ' ') || (*s == '\t') ) s--;
 s++; *s = 0;
 if ( l > *len ) *len = l;
 return b;

}

char **split(char *l, int c, int *longest) {

 int howmany, i;
 char **arr;
 char *n;
 if ( (n = strchr(l, 10)) != NULL ) *n = 0;
 if ( (n = strchr(l, 13)) != NULL ) *n = 0;
 for(howmany=1, i=0; l[i] != 0 ; i++) 
   if ( (l[i]==c) && ((i+1) < strlen(l))) howmany++;
 arr = malloc(sizeof(char *) * (howmany+1));
 arr[0] = NULL;
 if ( arr != NULL ) {
   n = l;
   for(i=0; i < howmany; i++) {
     arr[i] = n; 
     n = strchr(n, c);
     if ( n == NULL ) { break; }
     *n = 0; n++;
     arr[i] = trim(arr[i], longest);
   }
 }
 arr[howmany] = NULL;
 return arr;

}


  1. define MAXLINELEN 1024
  2. define FILLCHAR ' '

/* decide the alignment */ enum textalign {

 LEFT_ALIGNED, RIGHT_ALIGNED, CENTER_ALIGNED

}; const int alignment = CENTER_ALIGNED;


int main() {

 char buf[MAXLINELEN];
 line_t *head, *cur;
 char *lb;
 int reallongest, i, len, ipos;
 head = malloc(sizeof(line_t));
 memset(head, 0, sizeof(line_t));
 /* for each line, split it ($-separated words) */
 cur = head;
 while ( fgets(buf, MAXLINELEN, stdin) != NULL ) {
   lb = malloc(strlen(buf));
   strncpy(lb, buf, strlen(buf));
   cur->wordlist = split(lb, '$', &(cur->longest));
   cur->next = malloc(sizeof(line_t));
   memset(cur->next, 0, sizeof(line_t));
   cur = cur->next;
 }
 cur->next = NULL; /* last node is a end-marker */
 /* each line holds the longest word length; find the
    longest among all lines; this determines the width
    of all columns */
 reallongest = head->longest;
 cur = head;
 while( cur->next != NULL ) {
   if ( cur->longest > reallongest )
     reallongest = cur->longest;
   cur = cur->next;
 }
 reallongest++;
 buf[reallongest] = 0; /* no bounds check... */
 /* print the columns */
 cur = head;
 while( cur->next != NULL ) {
   for(i=0; cur->wordlist[i] != NULL; i++) {
     len = strlen(cur->wordlist[i]);
     switch(alignment) {
     case LEFT_ALIGNED:

ipos = 0; break;

     case RIGHT_ALIGNED:

ipos = reallongest - len; break;

     case CENTER_ALIGNED:

ipos = (reallongest - len)/2; break;

     }
     memset(buf, FILLCHAR, reallongest);
     memcpy(buf+ipos, cur->wordlist[i], len);
     printf("%s ", buf);
   }
   printf("\n");
   cur = cur->next;
 }

}</lang>

C++

The following code fragments are all in one source file for this example (and in this order), but broken up here for clarity.

A reusable template function that handles the tokenizing, and is independent of any work that might wish to be done with the results:

<lang cpp>#include <vector>

  1. include <string> // for getline etc.
  2. include <iostream>
  3. include <sstream> // for istringstream
  4. include <algorithm> // for max
  5. include <iomanip> // for setw
  6. include <fstream> // for ofstream

using namespace std;

template< typename C > void enumerateFields( const string& strInput, char chDelim, C callback ) {

   istringstream issFile( strInput );
   string strLine;
   string strField;
   size_t nColIndex;
   while ( getline( issFile, strLine ) )
   {
       istringstream issLine( strLine );
       nColIndex = 0;
       while ( getline( issLine, strField, chDelim ) )
       {
           callback( nColIndex, strField );
           nColIndex++;
       }
   }

}</lang>

A function object that fills an array with column widths:

<lang cpp>typedef vector< size_t > ColWidths;

struct MaxColWidthsDeterminer {

   explicit MaxColWidthsDeterminer( ColWidths& colWidths )
   : m_colWidths( colWidths ) {}
   void operator()( size_t nColIndex, const string& strField );
   ColWidths& m_colWidths;

};

void MaxColWidthsDeterminer::operator()( size_t nColIndex,

                                        const string& strField )

{

   size_t nWidth = strField.length();
   if ( nColIndex >= m_colWidths.size() )
       m_colWidths.push_back( nWidth );
   else
       m_colWidths[ nColIndex ] = max( m_colWidths[ nColIndex ], nWidth );

}</lang>

A function object that outputs fields formatted in columns:

<lang cpp>struct FormattedLister {

   enum Alignment { eLeft, eRight, eCenter };
   FormattedLister( const ColWidths& colWidths, ostream& os,
                    Alignment alignment = eLeft )
   : m_colWidths( colWidths ), m_os( os ), m_alignment( alignment ),
     m_nPrevColIndex( 0 )
   {
       m_savedStreamFlags = os.flags();
       m_os.setf( ( m_alignment == eRight ) ? ios::right : ios::left,
                  ios::adjustfield );
   }
   ~FormattedLister()
   {
       m_os.flags( m_savedStreamFlags );
   }
   void operator()( size_t nColIndex, const string& strField );
   const ColWidths& m_colWidths;
   ostream&         m_os;
   Alignment        m_alignment;
   size_t           m_nPrevColIndex;
   ios::fmtflags    m_savedStreamFlags;

};

void FormattedLister::operator()( size_t nColIndex, const string& strField ) {

   if ( nColIndex < m_nPrevColIndex )
       m_os << '\n';
   if ( m_alignment == eCenter )
   {
       size_t nSpacesBefore = ( m_colWidths[ nColIndex ] - strField.length() )
                              / 2;
       size_t nSpacesAfter = m_colWidths[ nColIndex ] - strField.length()
                             - nSpacesBefore + 1;
       m_os << string( nSpacesBefore, ' ' ) << strField
            << string( nSpacesAfter, ' ' );
   }
   else
   {
       m_os << setw( static_cast< streamsize >( m_colWidths[ nColIndex ] ) )
            << strField << ' ';
   }
   m_nPrevColIndex = nColIndex;

}</lang>

The test program, that makes a pass through the data to determine the column widths, and then three more for outputting in each of the three column alignments:

<lang cpp>int main() {

   const string strInput( 
       "Given$a$text$file$of$many$lines,$where$fields$within$a$line$\n"
       "are$delineated$by$a$single$'dollar'$character,$write$a$program\n"
       "that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$\n"
       "column$are$separated$by$at$least$one$space.\n"
       "Further,$allow$for$each$word$in$a$column$to$be$either$left$\n"
       "justified,$right$justified,$or$center$justified$within$its$column." );
   const char chDelim = '$';
   ColWidths colWidths;
   enumerateFields( strInput, chDelim, MaxColWidthsDeterminer( colWidths ) );
   ofstream outFile( "ColumnAligner.txt" );
   if ( outFile )
   {
       enumerateFields( strInput, chDelim,
                        FormattedLister( colWidths, outFile ) );
       outFile << '\n';
       enumerateFields( strInput, chDelim,
                        FormattedLister( colWidths, outFile,
                                         FormattedLister::eRight ) );
       outFile << '\n';
       enumerateFields( strInput, chDelim,
                        FormattedLister( colWidths, outFile,
                                         FormattedLister::eCenter ) );
       outFile << endl;
   }

}</lang>

Common Lisp

<lang lisp>(defun nonempty (seq)

 (position-if (lambda (x) (declare (ignore x)) t) seq))

(defun split (delim seq) "Splits seq on delim into a list of subsequences. Trailing empty subsequences are removed."

 (labels
     ((f (seq &aux (pos (position delim seq)))
         (if pos
           (cons
             (subseq seq 0 pos)
             (f (subseq seq (1+ pos))))
           (list seq))))
   (let*
       ((list (f seq))
        (end (position-if #'nonempty list :from-end t)))
     (subseq list 0 (1+ end)))))

(defun lengthen (list minlen filler-elem &aux (len (length list))) "Destructively pads list with filler-elem up to minlen."

 (if (< len minlen)
   (nconc list (make-list
     (- minlen len) :initial-element filler-elem))
   list))

(defun align-columns (text &key (align :left) &aux

   (fmtmod (case align
     (:left "@")
     (:right ":")
     (:center "@:")
     (t (error "Invalid alignment."))))
   (fields (mapcar
     (lambda (line) (split #\$ line))
     (split #\Newline text)))
   (mostcols (loop for l in fields maximize (length l)))
   widest)
 (setf fields (mapcar
   (lambda (l) (lengthen l mostcols ""))
   fields))
 (setf widest (loop
   for col below (length (first fields))
   collect (loop
     for row in fields maximize (length (elt row col)))))
 (format nil
   (with-output-to-string (s)
     (princ "~{~{" s)
     (dolist (w widest)
       (format s "~~~d~a<~~a~~>" (1+ w) fmtmod))
     (princ "~}~%~}" s))
   fields))</lang>

E

<lang e> pragma.enable("accumulator")

def left(width, word) {

 return word + " " * (width - word.size())

}

def center(width, word) {

 def leftCount := (width - word.size()) // 2
 return " " * leftCount + word + " " * (width - word.size() - leftCount)

}

def right(width, word) {

 return " " * (width - word.size()) + word

}

def alignColumns(align, text) {

   def split := accum [] for line in text.split("\n") { _.with(line.split("$")) }
   var widths := []
   for line in split {
     for i => word in line {
       widths with= (i, widths.fetch(i, fn{0}).max(word.size()))
     }
   }
   return accum "" for line in split { 
     _ + accum "" for i => word in line {
       _ + align(widths[i] + 1, word)
     } + "\n"
   }

}</lang>

<lang e>? def text := "Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column."; null

? println(alignColumns(left, text)) Given a text file of many lines, where fields within a line are delineated by a single 'dollar' character, write a program that aligns each column of fields by ensuring that words in each column are separated by at least one space. Further, allow for each word in a column to be either left justified, right justified, or center justified within its column.

? println(alignColumns(center, text))

  Given        a        text     file    of      many     lines,     where   fields  within    a   line  
   are    delineated     by        a   single  'dollar' character,   write     a    program 
  that      aligns      each    column   of     fields      by     ensuring   that   words    in   each  
 column       are     separated   by     at     least       one     space.  
Further,     allow       for     each   word      in         a      column     to      be   either left  

justified, right justified, or center justified within its column.

? println(alignColumns(right, text))

     Given          a       text   file     of      many     lines,    where  fields  within      a line 
       are delineated         by      a single  'dollar' character,    write       a program
      that     aligns       each column     of    fields         by ensuring    that   words     in each 
    column        are  separated     by     at     least        one   space.
  Further,      allow        for   each   word        in          a   column      to      be either left 
justified,      right justified,     or center justified     within      its column.</lang>

Groovy

Solution: <lang groovy>def alignColumns = { align, rawText ->

   def lines = rawText.tokenize('\n')
   def words = lines.collect { it.tokenize(/\$/) }
   def maxLineWords = words.collect {it.size()}.max()
   words = words.collect { line -> line + [] * (maxLineWords - line.size()) }
   def columnWidths = words.transpose().collect{ column -> column.collect { it.size() }.max() }
   def justify = [   Right  : { width, string -> string.padLeft(width) },
                           Left   : { width, string -> string.padRight(width) },
                           Center : { width, string -> string.center(width) }      ]
   def padAll = { pad, colWidths, lineWords -> [colWidths, lineWords].transpose().collect { pad(it) + ' ' } }
   words.each { padAll(justify[align], columnWidths, it).each { print it }; println() }

}</lang>

Test Program: <lang groovy>def rawTextInput = Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column.

['Left', 'Center', 'Right'].each { align ->

   println "${align} Justified:"
   alignColumns(align, rawTextInput)
   println()

}</lang>

Output:

Left Justified:
Given      a          text       file   of     many      lines,     where    fields  within  a      line 
are        delineated by         a      single 'dollar'  character, write    a       program             
that       aligns     each       column of     fields    by         ensuring that    words   in     each 
column     are        separated  by     at     least     one        space.                               
Further,   allow      for        each   word   in        a          column   to      be      either left 
justified, right      justified, or     center justified within     its      column.                     

Center Justified:
  Given        a         text     file    of     many      lines,    where   fields  within    a    line 
   are     delineated     by       a    single 'dollar'  character,  write      a    program             
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each 
  column      are     separated    by     at     least      one      space.                              
 Further,    allow       for      each   word     in         a       column    to      be    either left 
justified,   right    justified,   or   center justified   within     its    column.                     

Right Justified:
     Given          a       text   file     of      many     lines,    where  fields  within      a line 
       are delineated         by      a single  'dollar' character,    write       a program             
      that     aligns       each column     of    fields         by ensuring    that   words     in each 
    column        are  separated     by     at     least        one   space.                             
  Further,      allow        for   each   word        in          a   column      to      be either left 
justified,      right justified,     or center justified     within      its column.

Haskell

<lang haskell> import Data.List import Control.Monad import Control.Arrow

dat = "Given$a$text$file$of$many$lines,$where$fields$within$a$line$\n" ++

     "are$delineated$by$a$single$'dollar'$character,$write$a$program\n" ++
     "that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$\n" ++
     "column$are$separated$by$at$least$one$space.\n" ++
     "Further,$allow$for$each$word$in$a$column$to$be$either$left$\n" ++
     "justified,$right$justified,$or$center$justified$within$its$column.\n"

brkdwn = unfoldr (\xs -> if null xs then Nothing else Just (second (drop 1) (span ('$'/=) xs)) ) spas = repeat ' '

format j ls = map (concat. zipWith align colw) rows

 where
   rows = map brkdwn $ lines ls
   colw = map (maximum. map length) . transpose $ rows
   align cw w =
     case j of
       'c' -> (take l spas) ++ w ++ (take (succ r) spas)
       'r' -> (take dl spas)++w++" "
       'l' -> w ++ (take (succ dl) spas) 
       where
          dl = cw-length w
          hdl = dl `div` 2
          (l,r)
           | odd dl    = (hdl,hdl+1)
           | otherwise = (hdl,hdl)

</lang> output example:

*Main> mapM_ putStrLn $ format 'c' dat
  Given        a         text     file    of     many      lines,    where   fields  within    a    line
   are     delineated     by       a    single 'dollar'  character,  write      a    program
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each
  column      are     separated    by     at     least      one      space.
 Further,    allow       for      each   word     in         a       column    to      be    either left
justified,   right    justified,   or   center justified   within     its    column.

J

Solution, using 'LEFT CENTER RIGHT'=:i.3 to specify justification:

  _2 {:\ ": <;._2@:,~&>/ '$';LF;text [ 9!:17 ,~ CENTER [ 9!:7 ' '$~11

Example:

   text=: noun define
Given$a$text$file$of$many$lines,$where$fields$within$a$line$
are$delineated$by$a$single$'dollar'$character,$write$a$program
that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$
column$are$separated$by$at$least$one$space.
Further,$allow$for$each$word$in$a$column$to$be$either$left$
justified,$right$justified,$or$center$justified$within$its$column.
)
   _2 {:\ ": <;._2@:,~&>/'$';LF;text [ 9!:17 ,~ CENTER [ 9!:7 ' '$~11
  Given        a         text     file    of     many      lines,    where   fields  within    a    line  
   are     delineated     by       a    single 'dollar'  character,  write      a    program              
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each  
  column      are     separated    by     at     least      one      space.                               
 Further,    allow       for      each   word     in         a       column    to      be    either left  
justified,   right    justified,   or   center justified   within     its    column.

OCaml

<lang ocaml>#load "str.cma" open Str

let input = "\ Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column."

let () =

 let lines = split (regexp_string "\n") input in
 let fields_l = List.map (split (regexp_string "$")) lines in
 let fields_l = List.map Array.of_list fields_l in
 let n = (* number of columns *)
   List.fold_left
     (fun n fields -> max n (Array.length fields))
     0 fields_l
 in
 let pads = Array.make n 0 in
 List.iter (
   (* calculate the max padding for each column *)
   Array.iteri
     (fun i word -> pads.(i) <- max pads.(i) (String.length word))
 ) fields_l;
 let print f =
   List.iter (fun fields ->
     Array.iteri (fun i word ->
       f word (pads.(i) - (String.length word))
     ) fields;
     print_newline()
   ) fields_l;
 in
 (* left column-aligned output *)
 print (fun word pad ->
   let spaces = String.make pad ' ' in
   Printf.printf "%s%s " word spaces);
 (* right column-aligned output *)
 print (fun word pad ->
   let spaces = String.make pad ' ' in
   Printf.printf "%s%s " spaces word);
 (* center column-aligned output *)
 print (fun word pad ->
   let pad1 = pad / 2 in
   let pad2 = pad - pad1 in
   let sp1 = String.make pad1 ' ' in
   let sp2 = String.make pad2 ' ' in
   Printf.printf "%s%s%s " sp1 word sp2);
</lang>

Perl

<lang Perl>

  #/usr/bin/perl -w
  use strict ;
 
  die "Call : perl columnaligner.pl <inputfile> <printorientation>!\n" unless
       @ARGV == 2 ; #$ARGV[ 0 ] contains example file , $ARGV[1] any of 'left' , 'right' or 'center'
  die "last argument must be one of center, left or right!\n" unless
     $ARGV[ 1 ] =~ /center|left|right/ ;
  sub printLines( $$$ ) ;
  open ( INFILE , "<" , "$ARGV[ 0 ]" ) or die "Can't open $ARGV[ 0 ]!\n" ;
  my @lines = <INFILE> ;
  close ( INFILE ) ;
  map { chomp $_ } @lines ;
  my @fieldwidths = ( ) ;
  map { push @fieldwidths , length( $_ ) } split ( /\$/ , $lines[ 0 ] ) ;
  foreach my $i ( 1..$#lines ) {
    my @words = split( /\$/ , $lines[ $i ] ) ;
    foreach my $j ( 0..$#words ) {
       if ( $j <= $#fieldwidths ) {
          if ( length( $words[ $j ] ) > $fieldwidths[ $j ] ) {
                $fieldwidths[ $j ] = length( $words[ $j ] ) ;
          }
       }
      else {
          push @fieldwidths, length( $words[ $j ] ) ;
       }
    }
 }
 map { printLine( $_ , $ARGV[ 1 ] , \@fieldwidths ) } @lines ;
 ##################################################################    ####
 sub printLine {
    my $line = shift ;
    my $orientation = shift ;
    my $widthref = shift ;
    my @words = split( /\$/, $line ) ;
    foreach my $k ( 0..$#words ) {
       my $printwidth = $widthref->[ $k ] + 1 ;
       if ( $orientation eq 'center' ) {
          $printwidth++ ;
       }
       if ( $orientation eq 'left' ) {
          print $words[ $k ] ;
          for ( my $i = 0 ; $i < $printwidth - length( $words[ $k ]     ) ; $i++ ) {
             print " " ;
          }
       }
       if ( $orientation eq 'right' ) {
          for ( my $i = 0 ; $i < $printwidth - length( $words[ $k ]     ) ; $i++ ) {
             print " " ;
          }
          print $words[ $k ] ;
       }
       if ( $orientation eq 'center' ) {
          my $left = int( ( $printwidth - length( $words[ $k ] ) )     / 2 ) ;
          my $right = $printwidth - length( $words[ $k ] ) - $left      ;
          if ( $left == 1 ) {
             print " " ;
          }
          if ( $left > 1 ) {
             for ( my $i = 0 ; $i < $left ; $i++ ) {
                print " " ;
             }
          }
          print $words[ $k ] ;
          if ( $right == 1 ) {
             print " " ;
          }
          if ( $right > 1 ) {
             for ( my $i = 0 ; $i < $right ; $i++ ) {
                print " " ;
             }
          }
       }
    }
    print "\n" ;
 }                              

</lang>

Python

<lang python>from StringIO import StringIO

textinfile = Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column.

j2justifier = dict(L=str.ljust, R=str.rjust, C=str.center)

def aligner(infile, justification = 'L'):

  \
 Justify columns of textual tabular input where the row separator is the newline
 and the field separator is a 'dollar' character.
 justification can be L, R, or C; (Left, Right, or Centered).

 Return the justified output as a string
 
 assert justification in 'LRC', "justification can be L, R, or C; (Left, Right, or Centered)."
 justifier = j2justifier[justification]

 fieldsbyrow= [line.strip().split('$') for line in infile]
 # pad to same number of fields per row
 maxfields = max(len(row) for row in fieldsbyrow)
 fieldsbyrow = [fields + []*(maxfields - len(fields))
                   for fields in fieldsbyrow]
 # rotate
 fieldsbycolumn = zip(*fieldsbyrow)
 # calculate max fieldwidth per column
 colwidths = [max(len(field) for field in column)
              for column in fieldsbycolumn]
 # pad fields in columns to colwidth with spaces
 fieldsbycolumn = [ [justifier(field, width) for field in column]
                    for width, column in zip(colwidths, fieldsbycolumn) ]
 # rotate again
 fieldsbyrow = zip(*fieldsbycolumn)

 return "\n".join( " ".join(row) for row in fieldsbyrow)


for align in 'Left Right Center'.split():

 infile = StringIO(textinfile)
 print "\n# %s Column-aligned output:" % align
 print aligner(infile, align[0])</lang>

Example output:

# Left Column-aligned output:
Given      a          text       file   of     many      lines,     where    fields  within  a      line 
are        delineated by         a      single 'dollar'  character, write    a       program             
that       aligns     each       column of     fields    by         ensuring that    words   in     each 
column     are        separated  by     at     least     one        space.                               
Further,   allow      for        each   word   in        a          column   to      be      either left 
justified, right      justified, or     center justified within     its      column.                     

# Right Column-aligned output:
     Given          a       text   file     of      many     lines,    where  fields  within      a line 
       are delineated         by      a single  'dollar' character,    write       a program             
      that     aligns       each column     of    fields         by ensuring    that   words     in each 
    column        are  separated     by     at     least        one   space.                             
  Further,      allow        for   each   word        in          a   column      to      be either left 
justified,      right justified,     or center justified     within      its column.                     

# Center Column-aligned output:
  Given        a         text     file    of      many     lines,    where    fields  within   a    line 
   are     delineated     by       a    single  'dollar' character,  write      a    program             
   that      aligns      each    column   of     fields      by     ensuring   that   words    in   each 
  column      are     separated    by     at     least      one      space.                              
 Further,    allow       for      each   word      in        a       column     to      be   either left 
justified,   right    justified,   or   center justified   within     its    column.                     

R

This example is incorrect. Please fix the code and remove this message.

Details: Alignment in the rightmost columns is wrong.

<lang R>

  1. Read in text

lines <- readLines(tc <- textConnection("Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column.")); close(tc)

  1. Split words by the dollar

words <- strsplit(lines, "\\$")

  1. Reformat

maxlen <- max(sapply(words, length)) words <- lapply(words, function(x) {length(x) <- maxlen; x}) block <- matrix(unlist(words), byrow=TRUE, ncol=maxlen) block[is.na(block)] <- "" leftjust <- format(block) rightjust <- format(block, justify="right") centrejust <- format(block, justify="centre")

  1. Print

noquote(leftjust) noquote(rightjust) noquote(centrejust) </lang> Right justified output shown.

     [,1]       [,2]       [,3]       [,4]       [,5]       [,6]       [,7]       [,8]        [,9]       [,10]      [,11]      [,12]     
[1,]      Given          a       text       file         of       many     lines,      where      fields     within          a       line
[2,]        are delineated         by          a     single   'dollar' character,      write          a    program                      
[3,]       that     aligns       each     column         of     fields         by   ensuring       that      words         in       each
[4,]     column        are  separated         by         at      least        one     space.                                            
[5,]   Further,      allow        for       each       word         in          a     column         to         be     either       left
[6,] justified,      right justified,         or     center  justified     within        its      column.

Ruby

<lang ruby>require 'stringio'

textinfile = <<END Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column. END

J2justifier = {'L' => String.instance_method(:ljust),

              'R' => String.instance_method(:rjust),
              'C' => String.instance_method(:center)}

=begin Justify columns of textual tabular input where the record separator is the newline and the field separator is a 'dollar' character. justification can be L, R, or C; (Left, Right, or Centered).

Return the justified output as a string =end def aligner(infile, justification = 'L')

 justifier = J2justifier[justification]

 fieldsbyrow = infile.map {|line| line.strip.split('$')}
 # pad to same number of fields per record
 maxfields = fieldsbyrow.map {|row| row.length}.max
 fieldsbyrow.map! {|row|
   row + []*(maxfields - row.length)
 }
 # calculate max fieldwidth per column
 colwidths = fieldsbyrow.transpose.map {|column|
   column.map {|field| field.length}.max
 }
 # pad fields in columns to colwidth with spaces
 fieldsbyrow.map! {|row|
   row.zip(colwidths).map {|field, width|
     justifier.bind(field)[width]
   }
 }

 fieldsbyrow.map {|row| row.join(" ")}.join("\n")

end

for align in %w{Left Right Center}

 infile = StringIO.new(textinfile)
 puts "\n# %s Column-aligned output:" % align
 puts aligner(infile, align[0..0])

end</lang>

Example output:

# Left Column-aligned output:
Given      a          text       file   of     many      lines,     where    fields  within  a      line
are        delineated by         a      single 'dollar'  character, write    a       program            
that       aligns     each       column of     fields    by         ensuring that    words   in     each
column     are        separated  by     at     least     one        space.                              
Further,   allow      for        each   word   in        a          column   to      be      either left
justified, right      justified, or     center justified within     its      column.                    

# Right Column-aligned output:
     Given          a       text   file     of      many     lines,    where  fields  within      a line
       are delineated         by      a single  'dollar' character,    write       a program            
      that     aligns       each column     of    fields         by ensuring    that   words     in each
    column        are  separated     by     at     least        one   space.                            
  Further,      allow        for   each   word        in          a   column      to      be either left
justified,      right justified,     or center justified     within      its column.                    

# Center Column-aligned output:
  Given        a         text     file    of     many      lines,    where   fields  within    a    line
   are     delineated     by       a    single 'dollar'  character,  write      a    program            
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each
  column      are     separated    by     at     least      one      space.                             
 Further,    allow       for      each   word     in         a       column    to      be    either left
justified,   right    justified,   or   center justified   within     its    column.                    

Tcl

<lang tcl>package require Tcl 8.5

set text {Given$a$text$file$of$many$lines,$where$fields$within$a$line$ are$delineated$by$a$single$'dollar'$character,$write$a$program that$aligns$each$column$of$fields$by$ensuring$that$words$in$each$ column$are$separated$by$at$least$one$space. Further,$allow$for$each$word$in$a$column$to$be$either$left$ justified,$right$justified,$or$center$justified$within$its$column.}

array set max {} foreach line [split $text \n] {

   set col 0
   set thisline [split $line \$]
   lappend words $thisline
   foreach word $thisline {
       set max([incr col]) [expr {[info exists max($col)]
                                   ? max($max($col), [string length $word])
                                   : [string length $word]
                           }]
   }

}

proc justify {word position width} {

   switch -exact -- $position {
       left {
           return [format "%-*s" $width $word]
       }
       center {
           set lpadw [expr {($width - [string length $word])/2}]
           return [format "%s%-*s" [string repeat " " $lpadw] [incr width -$lpadw] $word]
       }
       right {
           return [format "%*s" $width $word]
       }
   }

}

foreach position {left center right} {

   foreach thisline $words {
       set col 0
       set line ""
       foreach word $thisline {
           append line [justify $word $position $max([incr col])] " "
       }
       puts [string trimright $line]
   }
   puts ""

}</lang> Output:

Given      a          text       file   of     many      lines,     where    fields  within  a      line
are        delineated by         a      single 'dollar'  character, write    a       program
that       aligns     each       column of     fields    by         ensuring that    words   in     each
column     are        separated  by     at     least     one        space.
Further,   allow      for        each   word   in        a          column   to      be      either left
justified, right      justified, or     center justified within     its      column.

  Given        a         text     file    of     many      lines,    where   fields  within    a    line
   are     delineated     by       a    single 'dollar'  character,  write      a    program
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each
  column      are     separated    by     at     least      one      space.
 Further,    allow       for      each   word     in         a       column    to      be    either left
justified,   right    justified,   or   center justified   within     its    column.

     Given          a       text   file     of      many     lines,    where  fields  within      a line
       are delineated         by      a single  'dollar' character,    write       a program
      that     aligns       each column     of    fields         by ensuring    that   words     in each
    column        are  separated     by     at     least        one   space.
  Further,      allow        for   each   word        in          a   column      to      be either left
justified,      right justified,     or center justified     within      its column.

Vedit macro language

This implementation converts the file currently being edited. The file can then be saved with different filename if required.

RS(10, "$")		// Field separator
#11 = 1			// Align: 1 = left, 2 = center, 3 = right

// Reset column widths. Max 50 columns
for (#1=40; #1<90; #1++) { #@1 = 0 }

// Find max width of each column
BOF
Repeat(ALL) {
    for (#1=40; #1<90; #1++) {
        Match(@10, ADVANCE)			// skip field separator if any
	#2 = Cur_Pos
	Search("|{|@(10),|N}", NOERR)		// field separator or end of line
	#3 = Cur_Pos - #2			// width of text
	if (#3 > #@1) { #@1 = #3 }
	if (At_EOL) { Break }
    }
    Line(1, ERRBREAK)
}

// Convert lines
BOF
Repeat(ALL) {
    for (#1=40; #1<90; #1++) {
	#2 = Cur_Pos
	Search("|{|@(10),|N}", NOERR)
	if (At_EOL==0) { Del_Char(Chars_Matched) }
	#3 = #@1 - Cur_Pos + #2			// number of spaces to insert
	#4 = 0
	if (#11 == 2) { #4 = #3/2; #3 -= #4 }	// Center
	if (#11 == 3) { #4 = #3;   #3 = 0 }	// Right justify
	Set_Marker(1, Cur_Pos)
	Goto_Pos(#2)
	Ins_Char(' ', COUNT, #4)		// add spaces before the word
	Goto_Pos(Marker(1))
	Ins_Char(' ', COUNT, #3+1)		// add spaces after the word
	if (At_EOL) { Break }
    }
    Line(1, ERRBREAK)
}

Example output:

-- Left:
Given      a          text       file   of     many      lines,     where    fields  within  a      line 
are        delineated by         a      single 'dollar'  character, write    a       program 
that       aligns     each       column of     fields    by         ensuring that    words   in     each 
column     are        separated  by     at     least     one        space.   
Further,   allow      for        each   word   in        a          column   to      be      either left 
justified, right      justified, or     center justified within     its      column. 

-- Center:
  Given        a         text     file    of     many      lines,    where   fields  within    a    line 
   are     delineated     by       a    single 'dollar'  character,  write      a    program 
   that      aligns      each    column   of    fields       by     ensuring  that    words    in   each 
  column      are     separated    by     at     least      one      space.  
 Further,    allow       for      each   word     in         a       column    to      be    either left 
justified,   right    justified,   or   center justified   within     its    column. 

-- Right:
     Given          a       text   file     of      many     lines,    where  fields  within      a line 
       are delineated         by      a single  'dollar' character,    write       a program 
      that     aligns       each column     of    fields         by ensuring    that   words     in each 
    column        are  separated     by     at     least        one   space. 
  Further,      allow        for   each   word        in          a   column      to      be either left 
justified,      right justified,     or center justified     within      its column.