File Coverage

blib/lib/Data/TableAutoSum.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Data::TableAutoSum;
2              
3 10     10   95502 use strict;
  10         22  
  10         470  
4 10     10   45 use warnings;
  10         17  
  10         846  
5              
6             require Exporter;
7              
8             our @ISA = qw(Exporter);
9              
10             # I export nothing, so there aren't any @EXPORT* declarations
11              
12             our $VERSION = '0.08';
13              
14 10     10   11610 use Params::Validate qw/:all/;
  10         213827  
  10         3181  
15 10     10   17078 use Regexp::Common;
  10         78045  
  10         61  
16 10     10   944279 use Set::Scalar;
  10         165990  
  10         587  
17 10     10   101 use List::Util qw/reduce/;
  10         21  
  10         1066  
18 10     10   17321 use Tie::CSV_File;
  0            
  0            
19             use Data::Compare;
20              
21             sub implies($$) {
22             my ($x, $y) = @_;
23             return !$x || ($x && $y);
24             }
25              
26             sub is_uniq(@) {
27             my %items;
28             foreach (@_) {
29             return 0 if $items{$_}++;
30             }
31             return 1;
32             }
33              
34             use constant ROW_COL_TYPE => {
35             type => SCALAR | ARRAYREF,
36             callbacks => {
37             # scalar value
38             'integer' => sub { implies !ref($_[0]) => $_[0] =~ $RE{num}{int} },
39             'greater than 0' => sub { implies !ref($_[0]) => ($_[0] > 0) },
40            
41             # array ref
42             'uniq identifiers' => sub { no strict 'refs';
43             implies ref($_[0]) => is_uniq @{$_[0]} },
44             'some identifiers' => sub { no strict 'refs';
45             implies ref($_[0]) => @{$_[0]} }
46             }
47             };
48              
49             sub new {
50             my $proto = shift;
51             my %arg = validate( @_ => {rows => ROW_COL_TYPE, cols => ROW_COL_TYPE} );
52             my $class = ref($proto) || $proto;
53             my @rows = ref($arg{rows}) ? @{$arg{rows}} : (0 .. $arg{rows}-1);
54             my @cols = ref($arg{cols}) ? @{$arg{cols}} : (0 .. $arg{cols}-1);
55             my %data;
56             foreach my $row (@rows) {
57             foreach my $col (@cols) {
58             $data{$row}->{$col} = 0;
59             }
60             }
61             my $self = {
62             rows => \@rows,
63             rowset => Set::Scalar->new(@rows),
64             cols => \@cols,
65             colset => Set::Scalar->new(@cols),
66             data => \%data
67             };
68             bless $self, $class;
69             }
70              
71             sub rows {
72             my $self = shift;
73             return @{$self->{rows}};
74             }
75              
76             sub cols {
77             my $self = shift;
78             return @{$self->{cols}};
79             }
80              
81             sub data : lvalue {
82             my $self = shift;
83             my ($row, $col, $value) = validate_pos( @_,
84             {type => SCALAR,
85             callbacks => {'is a row' => sub {$self->{rowset}->contains(shift())}}
86             },
87             {type => SCALAR,
88             callbacks => {'is a col' => sub {$self->{colset}->contains(shift())}}
89             },
90             0
91             );
92             $self->{data}->{$row}->{$col} = $value if defined $value;
93             $self->{data}->{$row}->{$col};
94             }
95              
96             sub as_string {
97             my $self = shift;
98             my $output = join "\t", "", $self->cols, "Sum\n";
99             foreach my $row ($self->rows) {
100             $output .= $row . "\t";
101             $output .= join "\t", map {$self->data($row,$_)} ($self->cols);
102             $output .= "\t" . $self->rowresult($row) . "\n";
103             }
104             $output .= join "\t", "Sum", map {$self->colresult($_)} $self->cols;
105             $output .= "\t" . $self->totalresult . "\n";
106             return $output;
107             }
108              
109             sub store {
110             my ($self, $filename) = @_;
111             open FILE, ">$filename" or die "Can't open $filename to store the table: $!";
112             print FILE $self->as_string;
113             close FILE;
114             return $self;
115             }
116              
117             sub read {
118             my ($class, $filename) = @_;
119             tie my @data, 'Tie::CSV_File', $filename, sep_char => "\t",
120             quote_char => undef,
121             escape_char => undef;
122              
123            
124             my @header = @{ $data[0] };
125             my @col = @header[1 .. $#header-1];
126             my @row = map {$data[$_]->[0]} (1 .. $#data-1);
127             my $table = $class->new(rows => \@row, cols => \@col);
128            
129             foreach my $i (0 .. $#row) {
130             foreach my $j (0 .. $#col) {
131             $table->data($row[$i],$col[$j]) = $data[$i+1][$j+1];
132             }
133             }
134            
135             untie @data;
136             return $table;
137             }
138              
139             sub change {
140             my ($self, $sub) = @_;
141             foreach my $row ($self->rows) {
142             foreach my $col ($self->cols) {
143             local $_ = $self->data($row,$col);
144             &$sub;
145             $self->data($row,$col) = $_;
146             }
147             }
148             }
149              
150             sub merge {
151             my ($class, $sub, $table1, $table2) = @_;
152             my @row = $table1->rows;
153             my @col = $table1->cols;
154             my $merged = $class->new(rows => \@row, cols => \@col);
155             foreach my $i (@row) {
156             foreach my $j (@col) {
157             $merged->data($i,$j) = $sub->($table1->data($i,$j), $table2->data($i,$j));
158             }
159             }
160             return $merged;
161             }
162              
163             sub _calc_data {
164             my $result = $_[0];
165             $result += $_[$_] for (1 .. $#_);
166             return $result;
167             }
168              
169             sub rowresult {
170             my ($self, $row) = @_;
171             return _calc_data( map {$self->data($row,$_)} $self->cols );
172             }
173              
174             sub colresult {
175             my ($self, $col) = @_;
176             return _calc_data( map {$self->data($_,$col)} $self->rows );
177             }
178              
179             sub totalresult {
180             my $self = shift;
181             return _calc_data( map {values %$_} values %{$self->{data}} );
182             }
183              
184             sub contains_row {
185             my ($self, $row) = @_;
186             $self->{rowset}->contains($row);
187             }
188              
189             sub contains_col {
190             my ($self, $col) = @_;
191             $self->{colset}->contains($col);
192             }
193              
194             sub is_equal {
195             my ($self, $other) = @_;
196             Compare( [$self->rows], [$other->rows] ) &&
197             Compare( [$self->cols], [$other->cols] ) or return 0;
198             foreach my $row ($self->rows) {
199             foreach my $col ($self->cols) {
200             $self->data($row,$col) == $other->data($row,$col) or return 0;
201             }
202             }
203             1;
204             }
205              
206             1;
207             __END__