File Coverage

blib/lib/Data/Cube.pm
Criterion Covered Total %
statement 150 166 90.3
branch 34 50 68.0
condition 8 12 66.6
subroutine 25 27 92.5
pod 0 19 0.0
total 217 274 79.2


line stmt bran cond sub pod time code
1             package Data::Cube;
2 2     2   33375 use 5.008005;
  2         6  
  2         67  
3 2     2   10 use strict;
  2         6  
  2         65  
4 2     2   20 use warnings;
  2         4  
  2         54  
5 2     2   10 use Carp;
  2         3  
  2         125  
6 2     2   2555 use Data::Dumper;
  2         8541  
  2         132  
7 2     2   1947 use Data::Nest;
  2         1882  
  2         108  
8 2     2   14 use Scalar::Util qw/looks_like_number/;
  2         5  
  2         3384  
9              
10             our $VERSION = "0.02";
11              
12             sub new {
13 13     13 0 13064 my $self = shift;
14              
15 13         25 my @dims = @_;
16              
17             bless {
18             cells => {},
19              
20             dims => [@dims],
21             currentdims => [@dims],
22             records => [],
23 110     110   5196 measures => {"count", sub { my @data = @_; scalar @data;}},
  110         273  
24 13         151 hiers => {},
25             invHiers => {},
26             cells => undef,
27             }, $self;
28             };
29              
30             # cubeの複製
31             sub clone {
32 3     3 0 958 my $self = shift;
33 3         10 my $cube = new Data::Cube();
34 3         8 $cube->{dims} = $self->{dims};
35 3         4 $cube->{currentdims} = $self->{currentdims};
36 3         6 $cube->{measures} = $self->{measures};
37 3         12 $cube->{hiers} = $self->{hiers};
38              
39 3         8 $cube;
40             }
41              
42             ############################################################
43             # utility functions
44             #
45             # 文字列/数値を比較する
46             sub is_same {
47 112     112 0 155 my $self = shift;
48 112         127 my ($a, $b) = @_;
49 112 100 66     396 if(looks_like_number($a) and looks_like_number($b)){
    50          
50 7 100       39 return 1 if $a == $b;
51             }elsif(looks_like_number($a)){
52 0 0       0 return 1 if $a == $b;
53             }else{
54 105 100       248 return 1 if $a eq $b;
55             }
56 67         203 0;
57             }
58              
59             # 条件フィルター
60             sub recordFilter {
61 91     91 0 86 my $self = shift;
62 91         74 my $cond = shift;
63 91         80 my $record = shift;
64              
65 91 50       148 return if(ref $cond ne "HASH");
66 91 50       128 return if(ref $record ne "HASH");
67              
68 91         134 for my $key (keys %$cond){
69 102 50       164 return 0 unless exists $record->{$key};
70 102         113 my $val = $cond->{$key};
71 102 100       179 if(ref $val eq "CODE"){
    100          
72 1 50       5 return 0 unless $val->($record->{$key});
73             }elsif(ref $val eq "ARRAY"){
74 1 50 33     10 return 0 unless ($val->[0] <= $record->{$key} and $record->{$key} <= $val->[1]);
75             }else{
76 100 100       175 return 0 unless $self->is_same($val, $record->{$key});
77             }
78             }
79 29         74 1;
80             }
81              
82             ############################################################
83             # data put
84             #
85             # データを追加する
86             sub put {
87 7     7 0 1028 my $self = shift;
88 7         14 my @data = @_;
89              
90 7 100 100     41 if(scalar @data == 1 and ref $data[0] eq 'ARRAY'){
91 5         6 @data = @{$data[0]};
  5         27  
92             }
93 7         18 for my $dat (@data){
94 180         142 push @{$self->{records}}, $dat;
  180         281  
95             }
96             };
97              
98             ############################################################
99             # dimension
100             #
101             # 次元の取得
102             sub get_dimension {
103 1     1 0 926 my $self = shift;
104 1         3 $self->{dims};
105             };
106              
107             # 現在の次元値の取得
108             sub get_current_dimension {
109 1     1 0 4 my $self = shift;
110 1         8 $self->{currentdims};
111             };
112              
113             # 現在の順序の変更
114             sub reorder_dimension {
115 0     0 0 0 my $self = shift;
116 0         0 $self;
117             }
118              
119             sub get_dimension_component {
120 3     3 0 9 my $self = shift;
121 3         6 my $dim = shift;
122 3 100       18 return {} unless $dim;
123              
124 2         3 my %components;
125 2         4 for my $record (@{$self->{records}}){
  2         5  
126 86 50       166 next unless exists $record->{$dim};
127 86 100       182 $components{$record->{$dim}} = 0 unless $components{$record->{$dim}};
128 86         114 $components{$record->{$dim}}++;
129             }
130 2         12 \%components;
131             }
132              
133             # 次元の追加
134             sub add_dimension {
135 1     1 0 7 my $self = shift;
136 1         2 my $dim = shift;
137 1         2 push @{$self->{dims}}, $dim;
  1         3  
138 1         2 push @{$self->{currentdims}}, $dim;
  1         3  
139 1         3 $self;
140             };
141              
142             # 次元の削除
143             sub remove_dimension {
144 1     1 0 904 my $self = shift;
145 1         3 my $rmdim = shift;
146 1         2 while(my ($i, $dim) = each @{$self->{dims}}){
  2         10  
147 2 100       6 if($self->is_same($dim, $rmdim)){
148 1         2 splice @{$self->{dims}}, $i, 1;
  1         4  
149 1         2 splice @{$self->{currentdims}}, $i, 1;
  1         2  
150 1         2 last;
151             }
152             }
153 1         3 $self;
154             };
155              
156             ############################################################
157             # hierarchy
158             #
159             # 階層の追加
160             sub add_hierarchy {
161 4     4 0 740 my $self = shift;
162 4         5 my $child = shift;
163 4         5 my $parent = shift;
164 4         6 my $rule = shift;
165              
166 4         9 $self->{hiers}{$parent} = $child;
167 4         8 $self->{invHiers}{$child} = $parent;
168              
169 4 100 66     26 if($rule and ref $rule eq "CODE"){
170 1         2 foreach my $record (@{$self->{records}}){
  1         3  
171 43         374 $record->{$parent} = $rule->($record->{$child});
172             }
173             }
174 4         17 $self;
175             }
176              
177             ############################################################
178             # measure
179             #
180             # 演算をセットする
181             sub add_measure {
182 2     2 0 6840 my $self = shift;
183 2         5 my $name = shift;
184 2         4 my $func = shift;
185              
186             # TODO: validation
187              
188 2         8 $self->{measures}{$name} = $func;
189 2         5 $self;
190             };
191              
192             ############################################################
193             # cube methods
194             #
195             # 一つの要素についてより詳細な分割を行う
196             sub drilldown {
197 2     2 0 13286 my $self = shift;
198 2         5 my @dims = @_;
199              
200 2         4 for my $dim (@dims){
201 2 50       8 if($self->{hiers}{$dim}){
202 2         3 my $cdim_cnt = 0;
203 2         3 for my $cdim (@{$self->{currentdims}}){
  2         6  
204 4 100       11 if ($self->is_same($cdim, $dim)){
205 2         5 $self->{currentdims}[$cdim_cnt] = $self->{hiers}{$dim};
206 2         3 $cdim_cnt++;
207             }
208             }
209             }
210             }
211 2         28 $self;
212             };
213              
214             # いくつかの要素を一つの要素にまとめ上げる
215             sub drillup {
216 1     1 0 2120 my $self = shift;
217 1         3 my @dims = @_;
218              
219 1         1 for my $dim (@dims){
220 1 50       5 if($self->{invHiers}{$dim}){
221 0         0 my $cdim_cnt = 0;
222 0         0 for my $cdim (@{$self->{currentdims}}){
  0         0  
223 0 0       0 if ($self->is_same($cdim, $dim)){
224 0         0 $self->{currentdims}[$cdim_cnt] = $self->{invHiers}{$dim};
225 0         0 $cdim_cnt++;
226             }
227             }
228             }
229             }
230 1         3 $self;
231             };
232              
233              
234             # ひとつの要素を固定し取り出す
235             sub slice {
236 1     1 0 6 my $self = shift;
237 1         3 my %cond = @_; # sliceの条件
238              
239 1         12 my $slicedDice = $self->dice(%cond)->rollup();
240 1 50       5 if($slicedDice){
241 1         4 return $slicedDice->[0];
242             }
243 0         0 return undef;
244              
245             };
246              
247             # サブキューブを取り出す
248             #
249             # $cube->dice(Country => "US", Product => "Pencil")
250             #
251             sub dice {
252 2     2 0 8 my $self = shift;
253 2         5 my %ranges = @_;
254              
255 2         5 my $subCube = $self->clone();
256 2         3 my @subRecords = grep { $self->recordFilter(\%ranges, $_); } @{$self->{records}};
  86         137  
  2         3  
257 2         5 $subCube->{records} = \@subRecords;
258 2         8 $subCube;
259             };
260              
261             # すべてのセルで演算を行う
262             sub rollup {
263 7     7 0 3376 my $self = shift;
264 7         16 my %opt = @_;
265              
266 7         9 my @Dims = @{$self->{currentdims}};
  7         18  
267              
268 7         33 my $nest = new Data::Nest(%opt);
269 7         86 foreach my $dim (@Dims){
270 14         88 $nest->key($dim);
271             }
272 7         49 foreach my $name (keys %{$self->{measures}}){
  7         20  
273 9         39 $nest->rollup($name, $self->{measures}{$name});
274             }
275 7         61 $nest->keyname("dim");
276 7         47 $self->{cells} = $nest->entries($self->{records});
277 7         167 $self->{cells};
278             };
279              
280             ############################################################
281             # measures
282             #
283             # 日付表示から月表示への変形
284             sub fromDateToMonth {
285 0     0 0   my $d = shift;
286 0           warn $d."\n";
287 0 0         if($d =~ /^(\d+)\/(\d+)\/(\d+)/){
288 0           my ($m, $d, $Y) = ($1, $2, $3);
289 0           return "$Y/$m";
290             }
291 0           undef;
292             };
293              
294             1;
295             __END__