File Coverage

blib/lib/Data/Freq/Field.pm
Criterion Covered Total %
statement 187 191 97.9
branch 115 134 85.8
condition 32 46 69.5
subroutine 26 26 100.0
pod 13 13 100.0
total 373 410 90.9


line stmt bran cond sub pod time code
1 5     5   171646 use 5.006;
  5         41  
2 5     5   24 use strict;
  5         7  
  5         246  
3 5     5   31 use warnings;
  5         8  
  5         284  
4              
5             package Data::Freq::Field;
6              
7             =head1 NAME
8              
9             Data::Freq::Field - Controls counting with Data::Freq at each level
10              
11             =head1 VERSION
12              
13             Version 0.04
14              
15             =cut
16              
17             our $VERSION = '0.04';
18              
19 5     5   36 use Carp qw(croak);
  5         9  
  5         229  
20 5     5   2005 use Date::Parse qw(str2time);
  5         31797  
  5         357  
21 5     5   38 use Scalar::Util qw(looks_like_number);
  5         9  
  5         10404  
22             require POSIX;
23              
24             =head1 METHODS
25              
26             =head2 new
27              
28             Usage:
29              
30             Data::Freq::Field->new({
31             type => 'text' , # { 'text' | 'number' | 'date' }
32             sort => 'count', # { 'value' | 'count' | 'first' | 'last' }
33             order => 'desc' , # { 'asc' | 'desc' }
34             pos => 0 , # { 0 | 1 | 2 | -1 | -2 | .. | [0, 1, 2] | .. }
35             key => 'mykey', # { any key(s) for input hash refs }
36             convert => sub {...},
37             });
38              
39             Constructs a field object.
40              
41             See L for details.
42              
43             =cut
44              
45             sub new {
46 287     287 1 71213 my ($class, $input) = @_;
47 287         499 my $self = bless {}, $class;
48            
49 287 100       835 if (!ref $input) {
    100          
    50          
50 102 50       186 $self->_extract_any($input) or croak "invalid argument: $input";
51             } elsif (ref $input eq 'HASH') {
52 107         204 for my $target (qw(type aggregate sort order pos key)) {
53 642 100       1066 if (defined $input->{$target}) {
54 206         334 my $method = "_extract_$target";
55            
56 206 50       495 $self->$method($input->{$target})
57             or croak "invalid $target: $input->{$target}";
58             }
59             }
60            
61 107         161 for my $target (qw(offset limit)) {
62 214 100       363 if (defined $input->{$target}) {
63 44         78 $self->{$target} = int($input->{$target});
64             }
65             }
66            
67 107         146 for my $target (qw(convert)) {
68 107 100       212 if (defined $input->{$target}) {
69 1         2 $self->{$target} = $input->{$target};
70            
71 1 50       5 if (ref $input->{$target} ne 'CODE') {
72 0         0 croak "$target must be a CODE ref";
73             }
74             }
75             }
76             } elsif (ref $input eq 'ARRAY') {
77 78         118 for my $item (@$input) {
78 173 50       326 $self->_extract_any($item) or croak "invalid argument: $item";
79             }
80             } else {
81 0         0 croak "invalid field: $input";
82             }
83            
84 287 100       528 $self->{type} = 'text' unless defined $self->type;
85 287   100     892 $self->{aggregate} ||= 'count';
86            
87 287 100       475 if ($self->type eq 'text') {
88 175   100     481 $self->{sort} ||= 'score';
89             } else {
90 112   100     259 $self->{sort} ||= 'value';
91             }
92            
93 287 100       1154 if ($self->{sort} =~ /^(count|score|last)$/) {
94 146   100     439 $self->{order} ||= 'desc';
95             } else {
96 141   100     296 $self->{order} ||= 'asc';
97             }
98            
99 287         867 return $self;
100             }
101              
102             =head2 evaluate_record
103              
104             Usage:
105              
106             my $field = Data::Freq::Field->new(...);
107             my $record = Data::Freq::Record->new(...);
108             my $normalized_text = $field->evaluate_record($record);
109              
110             Evaluates an input record as a normalized text that will be used for frequency counting,
111             depending on the parameters passed to the L method.
112              
113             This is intended to be an internal method for L.
114              
115             =cut
116              
117             sub evaluate_record {
118 249     249 1 349 my ($self, $record) = @_;
119 249         282 my $result = undef;
120            
121             TRY: {
122 249 100       268 if (defined $self->pos) {
  249 100       334  
    100          
    100          
123 129         207 my $pos = $self->pos;
124 129 50       211 my $array = $record->array or last TRY;
125 129         241 $result = "@$array[@$pos]";
126             } elsif (defined $self->key) {
127 2         4 my $key = $self->key;
128 2 50       6 my $hash = $record->hash or last TRY;
129 2         7 $result = "@$hash{@$key}";
130             } elsif ($self->type eq 'date') {
131 15         31 $result = $record->date;
132             } elsif ($self->type eq 'number') {
133 8 100       19 my $array = $record->array or last TRY;
134 7         13 $result = $array->[0];
135             } else {
136 95         169 $result = $record->text;
137             }
138            
139 248 100       409 last TRY unless defined $result;
140            
141 247 100       344 if ($self->type eq 'date') {
142 15 50       45 $result = looks_like_number($result) ? $result : str2time($result);
143 15 50       52 last TRY unless defined $result;
144 15         26 $result = POSIX::strftime($self->strftime, localtime $result);
145             }
146             }
147            
148 249 100       379 if ($self->convert) {
149 1         3 $result = $self->convert->($result);
150             }
151            
152 249         483 return $result;
153             }
154              
155             =head2 select_nodes
156              
157             Usage:
158              
159             my $raw_node_list = [values %{$parent_node->children}];
160             my $sorted_node_list = $field->select_nodes($raw_node_list);
161              
162             Sorts and reduces a list of nodes (Data::Freq::Node) at the corresponding depth
163             in the L,
164             depending on the parameters passed to the L method.
165              
166             This is intended to be an internal method for L.
167              
168             =cut
169              
170             sub select_nodes {
171 48     48 1 196 my ($self, $nodes, $subfield) = @_;
172 48         72 my $type = $self->type;
173 48         76 my $sort = $self->sort;
174 48         77 my $order = $self->order;
175            
176 48 100       92 if ($sort eq 'score') {
177 11 100       25 if ($subfield) {
178 8         12 $sort = $subfield->aggregate;
179             } else {
180 3         5 $sort = 'count';
181             }
182             }
183            
184 48         81 my @tuples = map {[$_, $_->$sort, $_->first]} @$nodes;
  168         343  
185            
186 48 100 100     156 if ($type ne 'number' && $sort eq 'value') {
187 28 100       55 if ($order eq 'asc') {
188 26 50       73 @tuples = CORE::sort {$a->[1] cmp $b->[1] || $a->[2] <=> $b->[2]} @tuples;
  122         235  
189             } else {
190 2 50       7 @tuples = CORE::sort {$b->[1] cmp $a->[1] || $a->[2] <=> $b->[2]} @tuples;
  6         13  
191             }
192             } else {
193 20 100       43 if ($order eq 'asc') {
194 9 50       31 @tuples = CORE::sort {$a->[1] <=> $b->[1] || $a->[2] <=> $b->[2]} @tuples;
  24         63  
195             } else {
196 11 50       33 @tuples = CORE::sort {$b->[1] <=> $a->[1] || $a->[2] <=> $b->[2]} @tuples;
  30         73  
197             }
198             }
199            
200 48         73 my @result = map {$_->[0]} @tuples;
  168         249  
201            
202 48 100 100     83 if (defined $self->offset || defined $self->limit) {
203 22 100       35 my $offset = defined $self->offset ? $self->offset : 0;
204 22 100       37 my $length = defined $self->limit ? $self->limit : scalar(@result);
205 22         47 @result = splice(@result, $offset, $length);
206             }
207            
208 48         178 return \@result;
209             }
210              
211             =head2 type
212              
213             Retrieves the C parameter.
214              
215             =head2 aggregate
216              
217             Retrieves the C parameter.
218              
219             =head2 sort
220              
221             Retrieves the C parameter.
222              
223             =head2 order
224              
225             Retrieves the C parameter.
226              
227             =head2 pos
228              
229             Retrieves the C parameter as an array ref.
230              
231             =head2 key
232              
233             Retrieves the C parameter as an array ref.
234              
235             =head2 limit
236              
237             Retrieves the C parameter.
238              
239             =head2 offset
240              
241             Retrieves the C parameter.
242              
243             =head2 strftime
244              
245             Retrieves the C parameter (L).
246              
247             =head2 convert
248              
249             Retrieves the C parameter.
250              
251             =cut
252              
253 1150     1150 1 2187 sub type {$_[0]{type }}
254 26     26 1 86 sub aggregate {$_[0]{aggregate}}
255 94     94 1 228 sub sort {$_[0]{sort }}
256 84     84 1 311 sub order {$_[0]{order }}
257 394     394 1 665 sub pos {$_[0]{pos }}
258 126     126 1 235 sub key {$_[0]{key }}
259 76     76 1 173 sub limit {$_[0]{limit }}
260 89     89 1 200 sub offset {$_[0]{offset }}
261 33     33 1 577 sub strftime {$_[0]{strftime}}
262 250     250 1 385 sub convert {$_[0]{convert }}
263              
264             sub _extract_any {
265 275     275   368 my ($self, $input) = @_;
266            
267 275         416 for my $target (qw(pos type aggregate sort order)) {
268 754         1242 my $method = "_extract_$target";
269 754 100       1433 return $self if $self->$method($input);
270             }
271            
272 0         0 return undef;
273             }
274              
275             sub _extract_type {
276 308     308   471 my ($self, $input) = @_;
277 308 50       612 return undef if ref($input);
278            
279 308 100 66     2435 if (!defined $input || $input eq '' || $input =~ /^texts?$/i) {
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
280 91         313 $self->{type} = 'text';
281 91         302 return $self;
282             } elsif ($input =~ /^num(ber)?s?$/i) {
283 38         84 $self->{type} = 'number';
284 38         113 return $self;
285             } elsif ($input =~ /\%/) {
286 3         10 $self->{type} = 'date';
287 3         6 $self->{strftime} = $input;
288 3         10 return $self;
289             } elsif ($input =~ /^years?$/i) {
290 6         14 $self->{type} = 'date';
291 6         9 $self->{strftime} = '%Y';
292 6         19 return $self;
293             } elsif ($input =~ /^month?s?$/i) {
294 12         28 $self->{type} = 'date';
295 12         20 $self->{strftime} = '%Y-%m';
296 12         38 return $self;
297             } elsif ($input =~ /^(date|day)s?$/i) {
298 39         132 $self->{type} = 'date';
299 39         53 $self->{strftime} = '%Y-%m-%d';
300 39         295 return $self;
301             } elsif ($input =~ /^hours?$/i) {
302 4         9 $self->{type} = 'date';
303 4         6 $self->{strftime} = '%Y-%m-%d %H';
304 4         10 return $self;
305             } elsif ($input =~ /^minutes?$/i) {
306 4         8 $self->{type} = 'date';
307 4         5 $self->{strftime} = '%Y-%m-%d %H:%M';
308 4         12 return $self;
309             } elsif ($input =~ /^(seconds?|time)?$/i) {
310 6         13 $self->{type} = 'date';
311 6         11 $self->{strftime} = '%Y-%m-%d %H:%M:%S';
312 6         15 return $self;
313             }
314            
315 105         260 return undef;
316             }
317              
318             sub _extract_aggregate {
319 113     113   161 my ($self, $input) = @_;
320 113 50 33     536 return undef if !defined $input || ref($input) || $input eq '';
      33        
321            
322 113 100       347 if ($input =~ /^uniq(ue)?$/) {
    100          
    100          
    100          
323 4         11 $self->{aggregate} = 'unique';
324 4         13 return $self;
325             } elsif ($input =~ /^max(imum)?$/) {
326 4         11 $self->{aggregate} = 'max';
327 4         11 return $self;
328             } elsif ($input =~ /^min(imum)?$/) {
329 4         17 $self->{aggregate} = 'min';
330 4         15 return $self;
331             } elsif ($input =~ /^av(g|e(rage)?)?$/) {
332 6         13 $self->{aggregate} = 'average';
333 6         20 return $self;
334             }
335            
336 95         202 return undef;
337             }
338              
339             sub _extract_sort {
340 160     160   231 my ($self, $input) = @_;
341 160 50 33     602 return undef if !defined $input || ref($input) || $input eq '';
      33        
342            
343 160 100       685 if ($input =~ /^values?$/i) {
    100          
    100          
    100          
    100          
344 49         80 $self->{sort} = 'value';
345 49         201 return $self;
346             } elsif ($input =~ /^counts?$/i) {
347 25         55 $self->{sort} = 'count';
348 25         115 return $self;
349             } elsif ($input =~ /^scores?$/i) {
350 17         29 $self->{sort} = 'score';
351 17         48 return $self;
352             } elsif ($input =~ /^(first|occur(rence)?s?)$/i) {
353 21         40 $self->{sort} = 'first';
354 21         57 return $self;
355             } elsif ($input =~ /^last$/i) {
356 15         26 $self->{sort} = 'last';
357 15         46 return $self;
358             }
359            
360 33         65 return undef;
361             }
362              
363             sub _extract_order {
364 83     83   165 my ($self, $input) = @_;
365 83 50 33     299 return undef if !defined $input || ref($input) || $input eq '';
      33        
366            
367 83 100       296 if ($input =~ /^asc(end(ing)?)?$/i) {
    50          
368 55         96 $self->{order} = 'asc';
369 55         140 return $self;
370             } elsif ($input =~ /^desc(end(ing)?)?$/i) {
371 28         47 $self->{order} = 'desc';
372 28         77 return $self;
373             }
374            
375 0         0 return undef;
376             }
377              
378             sub _extract_pos {
379 287     287   443 my ($self, $input) = @_;
380 287 100       571 return undef if !defined $input;
381            
382 281 100       1106 if (ref $input eq 'ARRAY') {
    100          
383 11   100     40 $self->{pos} ||= [];
384 11         27 push @{$self->{pos}}, @$input;
  11         23  
385 11         29 return $self;
386             } elsif ($input =~ /^-?\d+$/) {
387 30   100     142 $self->{pos} ||= [];
388 30         37 push @{$self->{pos}}, $input;
  30         61  
389 30         84 return $self;
390             }
391            
392 240         642 return undef;
393             }
394              
395             sub _extract_key {
396 9     9   16 my ($self, $input) = @_;
397 9 50       21 return undef if !defined $input;
398            
399 9   50     38 $self->{key} ||= [];
400 9 100       10 push @{$self->{key}}, (ref($input) eq 'ARRAY' ? @$input : ($input));
  9         28  
401 9         23 return $self;
402             }
403              
404             =head1 AUTHOR
405              
406             Mahiro Ando, C<< >>
407              
408             =head1 LICENSE AND COPYRIGHT
409              
410             Copyright 2012 Mahiro Ando.
411              
412             This program is free software; you can redistribute it and/or modify it
413             under the terms of either: the GNU General Public License as published
414             by the Free Software Foundation; or the Artistic License.
415              
416             See http://dev.perl.org/licenses/ for more information.
417              
418             =cut
419              
420             1;