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

Christer Ekholm <[email protected]> Sun, 13 Feb 2005 23:37:42 +0100
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
This was fun!

In my sollution I increase nr of rows in a loop until I find a number
in which the columns that nr of rows make fits. In the center of my
design is two arrays, both consisting only indexes from the original
wordlist. One for geting max wordlengths for a column, and one for the
words that make up a row of output.  The printing is made with one
single printf (per row) where the formatstring for printf is built
with info about the columns on that rows.

The solution requires use of the function max from List::Util.

My solutiion is wery similar to the one posted by Roger Burton West,
perhaps a bit easier to follow since it's commented :-)

#! /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);
    
    print_list(10, ());
    print_list(10, ("Hello"));
    print_list(10, ("Hello there my friend"));
    print_list(10, ("Hello there my friend", "How are you?", "I am fine thanks"));
    print_list(40, ("Hello there my friend", "How are you?", "I am fine thanks"));
}


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