[SPOILER] Perl 'Easy' Quiz of the Week #2005-2

Christer Ekholm <[email protected]> Thu, 17 Feb 2005 21:50:03 +0100
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
This was fun!

Here is yet another example. The main theme of this solution is to
traverse the original list in chunks of column-lengt and building two
representaions of an array while doing it.  And doing this repeadtedly
for a increased nr of rows, starting with the lowest possible (1),
until it fits in the specified width.

The [col][row] -representaion of the array is used for finding the
longest word in that column, and the [row][col] -representation is
used for the later printing, which is done with a single printf per
row. Both arrays consists only of indexes to the original list, so no
moving of elements is needed.


#! /usr/bin/perl

use strict;
use warnings;
use List::Util 'max';

{ # separate main-scoop from subroutines scoops
    my @strings = qw(Jonathan Sam Abby Daniel Julia Terrence Constance Al);
    print_list(1, @strings);
    print_list(30, @strings);
    print_list(40, @strings);
    print_list(80, @strings);
}


sub print_list {
    my $width = shift;
    return unless ( @_ );
    my @words = sort @_;

    # A easy-acess list of all lengths.
    my @lengths = map { length } @words;
    
  ROW: # Loop to find the minimum needed nr of rows.
    for my $row ( 0 .. $#words ) {
	my $i   = 0; # current index.
	my $c   = 0; # current column.
	my $tot = 0; # acumulated length needed for this row.
	my @col;     # $col[x] contains a list of indexes in @words to use in column x.
	my @row;     # $row[x] contains a list of indexes in @words to use in row x.
	my @len;     # $len[x] is the length of the longest word in column x.
	while ( $i < @words ) {
	    for ( 0 .. $row ) {
		$col[$c][$_] = $i;
		$row[$_][$c] = $i++; 
		last unless ( $i < @words );
	    }
	    # What is the highest wordlength in this column?
	    $len[$c] = max @lengths[@{$col[$c]}] ;

	    if ( ($tot += $len[$c]) > $width ) {
		# It didn't fit! we need more rows.
		next ROW;
	    }
	    $tot += 2; # add the two spaces separator.
	    $c++;
	}
	# Yes! it fits. Start printing.
	for my $line ( 0 .. $row ) {
	    # Create formatstring.
	    my $fmt;
	    for ( 0 .. $#{$row[$line]}-1 ) {
		# all formatcodes execept the last should make room for two extra spaces.
		$fmt .= "%-" . ($len[$_]+2) . "s" ;
	    }
	    $fmt .= "%s";
	    
	    # And finally, print this line.
	    printf "$fmt\n",@words[@{$row[$line]}];
	}
	return;
    }

    # No fit at all, just print a word on each line.
    print join("\n",@words),"\n";
}

__END__


-- 
 Christer