[SOLUTION] solution to qotw 2005/03

"Christian Dühl" <[email protected]> Mon, 14 Feb 2005 13:33:51 +0100 (MET)
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
Hi all,

here is my solution to qotw 2005/03.

My function print_list gets three parameters, first the wanted width, second
the (ordered) list and third a display parameter for horizontal or vertical
ordering of the listitems. The default is the 1 (vertical, as described in
the quest).

My function prints a line in the wanted length above the output and a line
with the real length under the output.

Here is an output for width 30, 'vertical' sort and the list from the quest:

------------------------------
Abby   Al       Constance
Daniel Jonathan Julia
Sam    Terrence
-------------------------

This is the output with 'horizontal' sorting:

------------------------------
Abby      Daniel   Sam
Al        Jonathan Terrence
Constance Julia
---------------------------


perhaps I should better switch the use of horizontal and vertical here ;)

Greetings, Christian.


Here is the code, I soul not add it as a file, because tghe mail came back
to my, why a ever...


#!/usr/bin/perl
use strict;
use warnings;


=head1 QUEST

Quest of the weak # 2005 / 03

=head1 AUTHOR

Christian Dühl ([email protected])

=head1 TASK

Write a subroutine called print_list that takes a number indicating
the width of the display as a number of characters, and a list of
strings, and prints the strings in a sorted columnar display, using as
many columns that will fit in the display for the given list and as few
rows as possible.

The longest string in one column should appear exactly two spaces away
from the beginning of the next column.  (The columns are not
necessarily the same width.)

Each column should include consecutive elements in the sorted list,
such that the item following the last item in column 1 appears at the
top of column 2, and similarly for the remaining columns.

For example, given:

 @strings = qw(Jonathan Sam Abby Daniel Julia Terrence Constance Al);

print_list(30, @strings) prints:

 Abby       Daniel    Sam
 Al         Jonathan  Terrence
 Constance  Julia

print_list(40, @strings) prints:

 Abby  Constance  Jonathan  Sam
 Al    Daniel     Julia     Terrence

print_list(80, @strings) prints:

 Abby  Al  Constance  Daniel  Jonathan  Julia  Sam  Terrence

And print_list(1, @strings) prints:

 Abby
 Al
 Constance
 Daniel
 Jonathan
 Julia
 Sam
 Terrence

For a real world example of a similar display, see the Unix/Linux command,
'ls'.  For an example of how to determine the actual width of a terminal
window in characters (not required for this quiz, but good to know), see
the Perl Cookbook, 1st or 2nd edition, recipe 15.4.

=cut


my @list = qw/Jonathan Sam Abby Daniel Julia Terrence Constance Al/;

for (30, 40, 80, 1, 15) {
    print_list($_, [ sort @list ]);
    print "\n";
}
print_list(30, [ sort @list ], 2);

exit;


sub print_list {
    my ($width, $list, $sortorder) = (@_, 1);

   
#-------------------------------------------------------------------------
    # calculate extremal widthes of the hole list:
   
#-------------------------------------------------------------------------
    my ($maxl, $minl) = minmax_list($list);

   
#-------------------------------------------------------------------------
    # calculate the minimal and maximal number of needed columns for the
    # solution:
   
#-------------------------------------------------------------------------
    my $colmax = int( $width / ($minl + 1) );
    my $colmin = int( $width / ($maxl + 1) );
    $colmax = $colmin = 1 if $colmax < 1;

   
#-------------------------------------------------------------------------
    # test all possible solutions until one is small enough:
   
#-------------------------------------------------------------------------
    COLSET: for my $col (reverse $colmin .. $colmax) {
       
#---------------------------------------------------------------------
        # calculate the columns:
       
#---------------------------------------------------------------------
        my $row = int( scalar @$list / $col );
        ++$row if $col * $row < scalar @list;
        my @columns = fill_columns($col, $row, $list, $sortorder);

       
#---------------------------------------------------------------------
        # calculate the width of the columns:
       
#---------------------------------------------------------------------
        my $w = 0;
        my @colwidth;
        for my $lst (@columns) {
            my ($wmax, $wmin) = minmax_list($lst);
            push @colwidth, $wmax;
            $w += $wmax + 1;                 # +1 : space after the column
        }
        --$w;                                # no space after the last
column

       
#---------------------------------------------------------------------
        # print solution if ok:
       
#---------------------------------------------------------------------
        if ($w <= $width or $col == $colmin) {
            #print "Solution found for width $width with width $w:\n";
            print '-' x $width, "\n"; # print line with the wanted width

            for my $r (0..$row-1) {
                for my $c (0..$col-1) {
                    if ($c < scalar @columns        and
                        $r < scalar @{$columns[$c]}    )
                    {
                        printf "%*s ", -$colwidth[$c], $columns[$c]->[$r];
                    }
                }
                print "\n";
            } 

            print '-' x $w, "\n"; # print line with the real width
            last COLSET;          # no more solutions must be checked now
        }
    }
} # sub print_list


sub minmax_list {
    my ($list) = @_;

    my $maxl = 0;
    my $minl = -1;

    for my $word (@$list) {
        my $l = length $word;
        $maxl = $l if $l > $maxl;
        $minl = $l if $minl == -1 or $l < $minl;
    }

    return $maxl, $minl;
} # sub minmax_list


sub fill_columns {
    my ($col, $row, $list, $sortorder) = @_;

    my @columns;
    my $c = my $r = 0;
    for my $index (0..$#$list) {
        # horizontal:
        if ($sortorder == 2) {
            push @{ $columns[$c] }, $list->[$index];
            ++$r;
            if ($r == $row) {
                $r = 0;
                ++$c;
            }
        }
        # vertical:
        elsif ($sortorder == 1) {
            push @{ $columns[$c] }, $list->[$index];
            ++$c;
            if ($c == $col) {
                $c = 0;
            }
        }
        else {
            die "Unknown sort order '$sortorder'";
        }
    }

    return @columns;
} # sub fill_columns