Need help with column printing

L

Lynn

Hi All,

I am having problems generating a report (Columns) from data
that is in the form of associative arrays of associative arrays.

I have provided an example of what I am trying to do. I the problem
is I am unable to align the percent column. Any advise as
how to fix this?

Thanks

Lynn

#!/app/bin/perl
use warnings;
use strict;
use diagnostics;
use Text::Table;

my %people = (
'hr' => {
'TOTAL' => 15,
'WIN' => 11,
'HP' => 4,
'BLOG' => 2
},
'jg' => {
'TOTAL' => 14,
'WIN' => 5,
'SGI' => 9,
'BLOG' => 4
},
'my' => {
'TOTAL' => 10,
'WIN' => 9,
'BLOG' => 1,
'HP' => 1
},
'gh' => {
'TOTAL' => 13,
'WIN' => 8,
'SUN' => 5,
'BLOG' => 1
}
);
use vars qw(@by_family $TOTAL $BLOG $HP $IBM $SGI $SUN $WIN $OTH);

my @stuff = qw(TOTAL BLOG HP IBM SGI SUN WIN OTH);

while ( my ( $item, $record ) = each %people ) {
$TOTAL += $record->{TOTAL} unless !( defined( $record->{TOTAL} ) );
$OTH += $record->{OTH} unless !( defined( $record->{OTH} ) );
$BLOG += $record->{BLOG} unless !( defined( $record->{BLOG} ) );
$HP += $record->{HP} unless !( defined( $record->{HP} ) );
$IBM += $record->{IBM} unless !( defined( $record->{IBM} ) );
$SGI += $record->{SGI} unless !( defined( $record->{SGI} ) );
$SUN += $record->{SUN} unless !( defined( $record->{SUN} ) );
$WIN += $record->{WIN} unless !( defined( $record->{WIN} ) );
}

push ( @by_family, $TOTAL, $BLOG, $HP, $IBM, $SGI, $SUN, $WIN, $OTH );

my @percent = ();

push ( @percent, sprintf( '%2d', ( 100 * $by_family[2] / $by_family[0] ) ) )
unless !( defined( $by_family[2] ) or '0' );
push ( @percent, sprintf( '%2d', ( 100 * $by_family[3] / $by_family[0] ) ) )
unless !( defined( $by_family[3] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[4] / $by_family[0] ) ) )
unless !( defined( $by_family[4] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[5] / $by_family[0] ) ) )
unless !( defined( $by_family[5] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[6] / $by_family[0] ) ) )
unless !( defined( $by_family[6] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[7] / $by_family[0] ) ) )
unless !( defined( $by_family[7] ) or '0');

my $tb = Text::Table->new( \"|", 'Name', \"|", @stuff );
$tb->add( $_, @{ $people{$_} }{@stuff} ) for sort keys
%people;
$tb->add( 'TOTAL:', @by_family );
$tb->add( 'PERCENT TOTAL:','N/A', 'N/A', @percent );
print $tb;
 
A

Anno Siegel

Lynn said:
Hi All,

I am having problems generating a report (Columns) from data
that is in the form of associative arrays of associative arrays.

I have provided an example of what I am trying to do. I the problem
is I am unable to align the percent column. Any advise as
how to fix this?

First off, thanks for supplying runnable code that shows the problem.
That makes my job a lot easier. (This is "my job" because I happen to
be the author of Text::Table.)

The alignment is messed up where you build the @percent array. See my
comment in the code.
#!/app/bin/perl
use warnings;
use strict;
use diagnostics;
use Text::Table;

my %people = (
'hr' => {
'TOTAL' => 15,
'WIN' => 11,
'HP' => 4,
'BLOG' => 2
},
'jg' => {
'TOTAL' => 14,
'WIN' => 5,
'SGI' => 9,
'BLOG' => 4
},
'my' => {
'TOTAL' => 10,
'WIN' => 9,
'BLOG' => 1,
'HP' => 1
},
'gh' => {
'TOTAL' => 13,
'WIN' => 8,
'SUN' => 5,
'BLOG' => 1
}
);
use vars qw(@by_family $TOTAL $BLOG $HP $IBM $SGI $SUN $WIN $OTH);

Why are you using package variables here? From the posted code, there
is no reason why these couldn't be lexicals like other variables.
my @stuff = qw(TOTAL BLOG HP IBM SGI SUN WIN OTH);

Putting the keys into a sequence is a good idea, but you're not making
good use of the list.
while ( my ( $item, $record ) = each %people ) {
$TOTAL += $record->{TOTAL} unless !( defined( $record->{TOTAL} ) );
$OTH += $record->{OTH} unless !( defined( $record->{OTH} ) );
$BLOG += $record->{BLOG} unless !( defined( $record->{BLOG} ) );
$HP += $record->{HP} unless !( defined( $record->{HP} ) );
$IBM += $record->{IBM} unless !( defined( $record->{IBM} ) );
$SGI += $record->{SGI} unless !( defined( $record->{SGI} ) );
$SUN += $record->{SUN} unless !( defined( $record->{SUN} ) );
$WIN += $record->{WIN} unless !( defined( $record->{WIN} ) );
}

If you *want* undef to be treated as zero, that's a good time to say

no warnings 'uninitialized';

and let Perl handle it.
push ( @by_family, $TOTAL, $BLOG, $HP, $IBM, $SGI, $SUN, $WIN, $OTH );

Why the push()? @by_family is empty, so an assignment would be clearer.

@by_family = ( $TOTAL, $BLOG, $HP, $IBM, $SGI, $SUN, $WIN, $OTH );
my @percent = ();

push ( @percent, sprintf( '%2d', ( 100 * $by_family[2] / $by_family[0] ) ) )
unless !( defined( $by_family[2] ) or '0' );

"unless !" is better written as "if". Also, "or '0'" does nothing to
the boolean value of the expression. The whole condition could be written

if defined $by_family[ 2];

....but it is wrong anyway. If a column isn't defined, you should push
a 0, or a "", or a "-", or anything onto the array. Otherwise the *next*
column will slip into its place. That's what messes up the alignment.
push ( @percent, sprintf( '%2d', ( 100 * $by_family[3] / $by_family[0] ) ) )
unless !( defined( $by_family[3] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[4] / $by_family[0] ) ) )
unless !( defined( $by_family[4] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[5] / $by_family[0] ) ) )
unless !( defined( $by_family[5] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[6] / $by_family[0] ) ) )
unless !( defined( $by_family[6] ) or '0');
push ( @percent, sprintf( '%2d', ( 100 * $by_family[7] / $by_family[0] ) ) )
unless !( defined( $by_family[7] ) or '0');
my $tb = Text::Table->new( \"|", 'Name', \"|", @stuff );
$tb->add( $_, @{ $people{$_} }{@stuff} ) for sort keys
%people;
$tb->add( 'TOTAL:', @by_family );
$tb->add( 'PERCENT TOTAL:','N/A', 'N/A', @percent );
print $tb;

You have made it unnecessary hard to get the alignment right. For the
"TOTAL" and "PERCENT TOTAL" lines you are using arrays, whiled you use
a hash for the fundamental data structure.

Build the "total" and "percent" structures after the model of your basic
structure, as hashes keyed on @stuff. Then it will be much easier to
keep things aligned. After the setup of %people, this is how I would
continue:

my @stuff = qw(TOTAL BLOG HP IBM SGI SUN WIN OTH);

my %by_family;
while ( my ( $item, $record) = each %people ) {
no warnings 'uninitialized';
$by_family{ $_} += $record->{ $_} for @stuff;
}

my %percent;
$percent{ $_} = 100 * $by_family{ $_} / $by_family{ TOTAL} for @stuff;
$_ = sprintf '%.0f', $_ for values %percent;

my $tb = Text::Table->new( \"|", 'Name', \"|", @stuff );
$tb->add( $_, @{ $people{$_} }{@stuff} ) for sort keys %people;
$tb->add( 'TOTAL:', @by_family{ @stuff} );
$tb->add( 'PERCENT TOTAL:', 'N/A', 'N/A', @percent{ @stuff[ 2 .. $#stuff]});
print $tb;

The percent data is calculated for two cases that aren't needed (TOTAL and
BLOG). It would have been more difficult to exclude them. Instead, we
exclude them using only @stuff[ 2 .. $#stuff]. Add two 'N/A's, skip
two items in @stuff... the columns clearly stay aligned.

Anno
 
T

thundergnat

Lynn said:
Hi All,

I am having problems generating a report (Columns) from data
that is in the form of associative arrays of associative arrays.

I have provided an example of what I am trying to do. I the problem
is I am unable to align the percent column. Any advise as
how to fix this?

Thanks

Lynn

The misalignment is due to the different handling of numbers and
strings. Your counts are all numbers but your percentages are strings.
If you make your percentages numbers too, they all line up.

Read the doc for Text::Aligner on which Text::Table is based.

Your data structures cry out for hashes or arrays too.



#!/app/bin/perl
use warnings;
use strict;
use diagnostics;
use Text::Table;

my %people = (
'hr' => {
'TOTAL' => 15,
'WIN' => 11,
'HP' => 4,
'BLOG' => 2
},
'jg' => {
'TOTAL' => 14,
'WIN' => 5,
'SGI' => 9,
'BLOG' => 4
},
'my' => {
'TOTAL' => 10,
'WIN' => 9,
'BLOG' => 1,
'HP' => 1
},
'gh' => {
'TOTAL' => 13,
'WIN' => 8,
'SUN' => 5,
'BLOG' => 1
}
);

my %counts;
my @percent = ();
my @totals = ();
my @stuff = qw(TOTAL BLOG HP IBM SGI SUN WIN OTH);

while ( my ( $item, $record ) = each %people ) {
for (@stuff){
$counts{$_} = 0 unless $counts{$_};
$counts{$_} += $record->{$_} if $record->{$_}
}
}

for ('HP','IBM','SGI','SUN','WIN','OTH'){
push ( @percent, int( 100 * $counts{$_} / $counts{TOTAL} ) );
}

for (@stuff){
push ( @totals, $counts{$_} );
}

my $tb = Text::Table->new( \"|", 'Name', \"|", @stuff );
$tb->add( $_, @{ $people{$_} }{@stuff} ) for sort keys
%people;
$tb->add( 'TOTAL:', @totals );
$tb->add( 'PERCENT TOTAL:', 'N/A', 'N/A', @percent );
print $tb;
 
L

Lynn

Anno said:
First off, thanks for supplying runnable code that shows the problem.
That makes my job a lot easier. (This is "my job" because I happen to
be the author of Text::Table.)
Anything I can do to get an answer I will do. Doesn't evernone do that?
after all I do want to get a reply to my question :)
The alignment is messed up where you build the @percent array. See my
comment in the code.
[snipped]
use vars qw(@by_family $TOTAL $BLOG $HP $IBM $SGI $SUN $WIN $OTH);

Why are you using package variables here? From the posted code, there
is no reason why these couldn't be lexicals like other variables.

Because I was getting the "Global symbol "whatever" requires explicit
package name errors.
It was the only way I knew how to get rid of the warnings.
my @stuff = qw(TOTAL BLOG HP IBM SGI SUN WIN OTH);

Putting the keys into a sequence is a good idea, but you're not making
good use of the list.
Ok[snipped]

If you *want* undef to be treated as zero, that's a good time to say

no warnings 'uninitialized';

and let Perl handle it.
I was unaware of this. When ever I start to develop a script I use the
use warnings line and leave warnings alone.
Why the push()? @by_family is empty, so an assignment would be
clearer.

@by_family = ( $TOTAL, $BLOG, $HP, $IBM, $SGI, $SUN, $WIN, $OTH );

Ok, I understand.
my @percent = ();

push ( @percent, sprintf( '%2d', ( 100 * $by_family[2] /
$by_family[0] ) ) ) unless !( defined( $by_family[2] ) or '0' );

"unless !" is better written as "if". Also, "or '0'" does nothing to
the boolean value of the expression. The whole condition could be
written

if defined $by_family[ 2];

yes, that would be cleaner
...but it is wrong anyway. If a column isn't defined, you should push
a 0, or a "", or a "-", or anything onto the array. Otherwise the
*next* column will slip into its place. That's what messes up the
alignment.

Ok, here is where I made my mistake.
[snipped incorrect code]

You have made it unnecessary hard to get the alignment right. For the
"TOTAL" and "PERCENT TOTAL" lines you are using arrays, whiled you use
a hash for the fundamental data structure.

Build the "total" and "percent" structures after the model of your
basic structure, as hashes keyed on @stuff. Then it will be much
easier to
keep things aligned. After the setup of %people, this is how I would
continue:
[snipped]
The percent data is calculated for two cases that aren't needed
(TOTAL and BLOG). It would have been more difficult to exclude them.
Instead, we
exclude them using only @stuff[ 2 .. $#stuff]. Add two 'N/A's, skip
two items in @stuff... the columns clearly stay aligned.

yep! This makes sense :)
I would like to thank you for taking the time to respond.
It's nice to know that newbies to perl/programming have a place
to go for help.

Lynn
 
A

Anno Siegel

thundergnat said:
The misalignment is due to the different handling of numbers and
strings. Your counts are all numbers but your percentages are strings.
If you make your percentages numbers too, they all line up.

That is not the reason. Text::Table (and Text::Aligner) treat all
data as strings. The problem was that the percentage line didn't
*have* data for all columns.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,262
Messages
2,571,059
Members
48,769
Latest member
Clifft

Latest Threads

Top