GertMT has asked for the wisdom of the Perl Monks concerning the following question:

The article Unix Review Column 15 (July 1997) gave me some good excercise to play around with using a hash. Until recently I relied heavily on a module to create a matrix from a multi-dimensional hash. With this article I get some idea how to achieve this without a module.

While trying to create a total-row (bytes-from) at the end and total-column (bytes-to) at the bottom I found a solution for the first but got myself in trouble with the second.

To my idea the structure as it is right now doesn't make this possible? How to make sure the right following order is achieved? Can somebody explain to me what went wrong. Maybe I should set this up in a completely different way?

Thanks, Gert

Below the code from the article (that had a script to generate sample data) and my attempt to create total-row/column

#!/usr/bin/perl use strict; use warnings; use diagnostics; my ( $from, $to, $hh, $mm, $bytes ) = (); my %from_to_bytes = (); while (<DATA>) { ( $from, $to, $hh, $mm, $bytes ) = /^(\S+) (\S+) (\d+):(\d+) (\d+) +$/ or ( warn "bad format on line $.: $_" ), next; $from_to_bytes{$from}{$to} += $bytes; } my %to_hosts = (); for my $from ( sort keys %from_to_bytes ) { my $second = $from_to_bytes{$from}; my @keys = keys %$second; @to_hosts{@keys} = (); } my @to_hosts = sort keys %to_hosts; printf "%10s:", "bytes to"; for (@to_hosts) { printf " %10s", $_; } print "\n"; my $total = (); for my $from ( sort keys %from_to_bytes ) { printf "%10s:", $from; for my $to (@to_hosts) { my $bytes = $from_to_bytes{$from}{$to} || "- none -"; printf " %10s", $bytes; { no warnings; $total += $bytes; } # Calculate total row } printf " %5s", "$total"; $total = 0; # otherwise totalrow acc +umulates print "\n"; } for my $to (sort keys %from_to_bytes){ printf "%10s", "$from_to_bytes{$to}"; } __DATA__ barney barney 08:24 91 fred wilma 05:51 92 fred fred 11:20 83 fred wilma 11:07 76 wilma fred 22:07 3 barney barney 13:22 93 fred fred 06:52 95 fred wilma 17:21 92 barney barney 07:35 95 barney fred 09:58 14 betty barney 12:34 59 wilma barney 09:59 18 betty betty 22:44 29 wilma betty 07:39 99 wilma fred 14:30 91 betty barney 01:54 95 wilma fred 15:45 2

Replies are listed 'Best First'.
Re: matrix - creating total row/column
by bichonfrise74 (Vicar) on Oct 13, 2009 at 22:06 UTC

    From how I understood your question, you want to get the total for each columns... So, I replaced this code
    for my $to (sort keys %from_to_bytes){ printf "%10s", "$from_to_bytes{$to}"; }
    with this one.
    my %total_col; for my $i (keys %from_to_bytes) { for my $j (keys %{ $from_to_bytes{$i} }) { $total_col{$j} += $from_to_bytes{$i}->{$j}; } } print " " x 11; for my $i (sort keys %total_col) { printf " %10s", "$total_col{$i}"; } print "\n";
    Although as you can see there is some more work needed in the formatting. Hope this helps.

      this worked out very nice. Thank you very much. A bit puzzled though..

      I understand that for my $i (keys %from_to_bytes) {should be read as: for all members of the family (Fred, Wilma and so on).

      However I remain in trouble reading the remaining two lines:

      for my $j (keys %{ $from_to_bytes{$i} }) { $total_col{$j} += $from_to_bytes{$i}->{$j};
      It works but I'd like to understand as well.

      Using hashes maybe efficient, learning how to use them takes some time for me.

Re: matrix - creating total row/column
by graff (Chancellor) on Oct 14, 2009 at 01:48 UTC
    In order to output column and row totals, I think the best way is to build it into the data structure in the first place. This means adding a little bit to the "while" loop that is reading the data:
    my %from_to_bytes; my %col_headings; while (<DATA>) { ( $from, $to, $hh, $mm, $bytes ) = /^(\S+) (\S+) (\d+):(\d+) (\d+) +$/ or ( warn "bad format on line $.: $_" ), next; $from_to_bytes{$from}{$to} += $bytes; $from_to_bytes{$from}{"~~TOTAL"} += $bytes; $from_to_bytes{"~~TOTAL"}{$to} += $bytes; $col_headings{$to} = undef; }
    By putting "~~" as the first two characters of the hash key for the column and row totals, those keys will come out last when you sort the keys for output (because tilde comes last in ascii collation). Then you just print the matrix in the normal way, with no further ado:
    print join("\t", '', sort keys %col_headings, 'Total'),"\n"; for my $from ( sort keys %from_to_bytes ) { print (( $from eq '~~TOTAL' ) ? 'Total' : $from ); print "\t$from_to_bytes{$from}{$_}" for (sort keys %{$from_to_byte +s{$from}}); }
    (not tested)