File Coverage

lib/Data/Range/Compare.pm
Criterion Covered Total %
statement 204 209 97.6
branch 48 56 85.7
condition 9 15 60.0
subroutine 46 48 95.8
pod 34 37 91.8
total 341 365 93.4


line stmt bran cond sub pod time code
1             package Data::Range::Compare;
2              
3 12     12   274208 use strict;
  12         27  
  12         320  
4 12     12   59 use warnings;
  12         22  
  12         397  
5 12     12   97 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  12         25  
  12         1214  
6 12     12   15152 use overload '""'=>\¬ation ,fallback=>1;
  12         11101  
  12         96  
7              
8             require Exporter;
9             $VERSION='1.031';
10              
11             @ISA=qw(Exporter);
12              
13 12     12   1088 use constant key_helper => 0;
  12         21  
  12         898  
14 12     12   62 use constant key_start => 1;
  12         20  
  12         552  
15 12     12   56 use constant key_end => 2;
  12         21  
  12         529  
16 12     12   56 use constant key_generated => 3;
  12         19  
  12         576  
17 12     12   61 use constant key_missing => 4;
  12         20  
  12         529  
18 12     12   151 use constant key_data => 5;
  12         17  
  12         31555  
19              
20             @EXPORT_OK=qw(
21             key_helper
22             key_start
23             key_end
24             key_generated
25             key_missing
26             key_data
27              
28             add_one
29             sub_one
30             cmp_values
31              
32             sort_largest_range_end_first
33             sort_largest_range_start_first
34             sort_smallest_range_start_first
35             sort_smallest_range_end_first
36             sort_in_consolidate_order
37             sort_in_presentation_order
38              
39             HELPER_CB
40             );
41              
42             %EXPORT_TAGS=(
43             KEYS=>[qw(
44             key_helper
45             key_start
46             key_end
47             key_generated
48             key_missing
49             key_data
50             )]
51              
52             ,ALL=>\@EXPORT_OK
53              
54             ,HELPER_CB=>[qw(HELPER_CB)]
55             ,HELPER=>[qw(add_one sub_one cmp_values)]
56             ,SORT=>[qw(
57             sort_largest_range_end_first
58             sort_largest_range_start_first
59             sort_smallest_range_start_first
60             sort_smallest_range_end_first
61             sort_in_consolidate_order
62             sort_in_presentation_order
63             )]
64             );
65              
66             sub new {
67 102     102 1 4062 my $s=shift @_;
68 102         378 bless [@_],$s;
69             }
70              
71 529     529 1 896 sub helper_cb { my ($s,$key,@args)=@_; $s->[key_helper]->{$key}->(@args) }
  529         1177  
72              
73 429     429 1 1402 sub range_start () { $_[0]->[key_start] }
74 779     779 1 1759 sub range_end () { $_[0]->[key_end] }
75              
76             sub notation {
77 99     99 1 19602 my $notation=join ' - ',$_[0]->range_start,$_[0]->range_end;
78 99         336 $notation;
79             }
80 0     0 1 0 sub helper_hash () { $_[0]->[key_helper] }
81 64     64 0 762 sub missing () {$_[0]->[key_missing] }
82 3     3 0 25 sub generated () {$_[0]->[key_generated] }
83              
84             sub data () {
85 0     0 1 0 my ($s)=@_;
86 0 0       0 return $s->[key_data] if ref($s->[key_data]);
87 0         0 $s->[key_data]={};
88 0         0 $s->[key_data]
89             }
90              
91             sub overlap ($) {
92 16     16 1 46 my ($range_a,$range_b)=@_;
93 16 100 66     34 return 1 if
94             $range_a->cmp_range_start($range_b)!=1
95             &&
96             $range_a->cmp_range_end($range_b)!=-1;
97 7 50 33     16 return 1 if
98             $range_a->helper_cb(
99             'cmp_values'
100             ,$range_a->range_start
101             ,$range_b->range_end
102             )!=1
103             &&
104             $range_a->helper_cb(
105             'cmp_values'
106             ,$range_a->range_end
107             ,$range_b->range_end
108             )!=-1;
109              
110 7 50 33     16 return 1 if
111             $range_b->cmp_range_start($range_a)!=1
112             &&
113             $range_b->cmp_range_end($range_a)!=-1;
114              
115 7 100 66     16 return 1 if
116             #$range_b->range_start <=$range_a->range_end
117             $range_a->helper_cb(
118             'cmp_values'
119             ,$range_b->range_start
120             ,$range_a->range_end
121             )!=1
122             &&
123             $range_a->helper_cb(
124             'cmp_values'
125             ,$range_b->range_end
126             ,$range_a->range_end
127             )!=-1;
128              
129             undef
130 6         29 }
131              
132 2     2 1 6 sub grep_overlap ($) { [ grep {$_[0]->overlap($_) } @{$_[1]} ] }
  4         11  
  2         4  
133 2 100   2 1 3 sub grep_nonoverlap ($) { [ grep { $_[0]->overlap($_) ? 0 : 1 } @{$_[1]} ] }
  4         9  
  2         5  
134              
135             sub contains_value ($) {
136 4     4 1 10 my ($s,$cmp)=@_;
137 4 100       12 return 0 if $s->helper_cb('cmp_values',$s->range_start,$cmp)==1;
138 3 100       8 return 0 if $s->helper_cb('cmp_values',$cmp,$s->range_end)==1;
139 2         7 1
140             }
141              
142 41     41 1 1072 sub next_range_start () { $_[0]->helper_cb('add_one',$_[0]->range_end) }
143 18     18 1 55 sub previous_range_end () { $_[0]->helper_cb('sub_one',$_[0]->range_start) }
144              
145             sub cmp_range_start($) {
146 125     125 1 643 my ($s,$cmp)=@_;
147 125         253 $s->helper_cb('cmp_values',$s->range_start,$cmp->range_start)
148             }
149              
150             sub cmp_range_end($) {
151 290     290 1 776 my ($s,$cmp)=@_;
152 290         523 $s->helper_cb('cmp_values',$s->range_end,$cmp->range_end)
153             }
154              
155             sub contiguous_check ($) {
156 20     20 1 29 my ($cmp_a,$cmp_b)=@_;
157 20         45 $cmp_a->helper_cb(
158             'cmp_values'
159             ,$cmp_a->next_range_start
160             ,$cmp_b->range_start
161             )==0
162             }
163              
164             sub cmp_ranges ($) {
165 12     12 1 453 my ($range_a,$range_b)=@_;
166 12         24 my $cmp=$range_a->cmp_range_start($range_b);
167 12 100       41 if($cmp==0) {
168 4         8 return $range_a->cmp_range_end($range_b);
169             }
170 8         44 return $cmp;
171             }
172              
173             sub HELPER_CB () {
174 9     9 0 128 add_one=>\&add_one
175             ,sub_one=>\&sub_one
176             ,cmp_values=>\&cmp_values
177             }
178              
179 34     34 1 104 sub add_one { $_[0] + 1 }
180 15     15 1 50 sub sub_one { $_[0] -1 }
181 410     410 1 1459 sub cmp_values { $_[0] <=> $_[1] }
182              
183             sub get_common_range {
184 1     1 1 6 my ($class,$helper,$ranges)=@_;
185              
186 1         5 my ($range_start)=sort sort_largest_range_start_first @$ranges;
187 1         3 my ($range_end)=sort sort_smallest_range_end_first @$ranges;
188              
189 1         3 new($class,
190             $helper
191             ,$range_start->range_start
192             ,$range_end->range_end
193             );
194             }
195              
196             sub get_overlapping_range {
197 7     7 1 22 my ($class,$helper,$ranges,%opt)=@_;
198              
199 7         24 my ($range_start)=sort sort_smallest_range_start_first @$ranges;
200 7         19 my ($range_end)=sort sort_largest_range_end_first @$ranges;
201              
202 7         17 my $obj=new($class,$helper,$range_start->range_start,$range_end->range_end);
203 7         16 $obj->[key_generated]=1;
204 7         16 $obj;
205             }
206              
207             sub sort_in_presentation_order ($$) {
208 4     4 1 6 my ($cmp_a,$cmp_b)=@_;
209 4         9 $cmp_a->cmp_ranges($cmp_b);
210             }
211              
212             sub sort_in_consolidate_order ($$) {
213 18     18 1 28 my ($range_a,$range_b)=@_;
214 18 100       34 $range_a->cmp_range_start($range_b)
215             ||
216             $range_b->cmp_range_end($range_a);
217             }
218              
219             sub sort_largest_range_end_first ($$) {
220 71     71 1 124 my ($range_a,$range_b)=@_;
221 71         131 $range_b->cmp_range_end($range_a)
222             }
223              
224             sub sort_smallest_range_start_first ($$) {
225 38     38 1 65 my ($range_a,$range_b)=@_;
226 38         74 $range_a->cmp_range_start($range_b)
227             }
228              
229             sub sort_smallest_range_end_first ($$) {
230 68     68 1 106 my ($range_a,$range_b)=@_;
231 68         144 $range_a->cmp_range_end($range_b)
232            
233             }
234              
235             sub sort_largest_range_start_first ($$) {
236 7     7 1 10 my ($range_a,$range_b)=@_;
237 7         15 $range_b->cmp_range_start($range_a)
238             }
239              
240             sub consolidate_ranges {
241 10     10 1 33 my ($class,$helper,$ranges,%opt)=@_;
242 10         35 @$ranges=sort sort_in_consolidate_order @$ranges;
243 10         17 my $cmp=shift @$ranges;
244 10         20 my $return_ref=[];
245 10         26 while( my $next=shift @$ranges) {
246 8 100       18 if($cmp->overlap($next)) {
247 6 50       13 my $overlap=$cmp->cmp_ranges($next)==0 ?
248             $cmp
249             :
250             $class->get_overlapping_range($helper,[$cmp,$next]);
251 6         31 $cmp=$overlap;
252              
253             } else {
254 2         3 push @$return_ref,$cmp;
255 2         7 $cmp=$next;
256             }
257            
258             }
259 10         21 push @$return_ref,$cmp;
260 10         24 $return_ref;
261             }
262              
263             sub fill_missing_ranges {
264 1     1 1 7 my ($class,$helper,$ranges,%args)=@_;
265 1         3 %args=(consolidate_ranges=>0,%args);
266              
267 1 50       5 $ranges=consolidate_ranges($helper,$ranges) if $args{consolidate_ranges};
268 1         2 my $return_ref=[];
269              
270 1         2 my $cmp=shift @$ranges;
271 1         3 while(my $next=shift @$ranges) {
272 1         2 push @$return_ref,$cmp;
273 1 50       10 unless($cmp->contiguous_check($next)) {
274 1         4 my $missing=new($class,
275             $helper
276             ,$cmp->next_range_start
277             ,$next->previous_range_end);
278 1         3 $missing->[key_missing]=1;
279 1         2 push @$return_ref,$missing;
280             }
281 1         4 $cmp=$next;
282             }
283              
284 1         2 push @$return_ref,$cmp;
285              
286 1         3 $return_ref;
287             }
288              
289             sub range_start_end_fill {
290 1     1 1 7 my ($class,$helper,$ranges,%opt)=@_;
291              
292             my ($range_start)=sort sort_smallest_range_start_first
293 1         3 map { $_->[0] } @$ranges;
  3         7  
294 1         3 $range_start=$range_start->range_start;
295             my ($range_end)=sort sort_largest_range_end_first
296 1         3 map { $_->[$#{$_}] } @$ranges;
  3         5  
  3         8  
297 1         10 $range_end=$range_end->range_end;
298            
299 1         3 foreach my $ref (@$ranges) {
300 3         4 my $first_range=$ref->[0];
301 3         5 my $last_range=$ref->[$#{$ref}];
  3         11  
302              
303 3 100       7 if($first_range->helper_cb(
304             'cmp_values'
305             ,$first_range->range_start
306             ,$range_start
307             )!=0) {
308 2         5 my $new_range=new($class,
309             $helper
310             ,$range_start
311             ,$first_range->previous_range_end
312             );
313 2         4 unshift @$ref,$new_range;
314 2         4 $new_range->[key_missing]=1;
315 2         3 $new_range->[key_generated]=1;
316             }
317              
318 3 100       7 if($last_range->helper_cb('cmp_values'
319             ,$last_range->range_end
320             ,$range_end)!=0
321             ) {
322 2         5 my $new_range=new($class,
323             $helper
324             ,$last_range->next_range_start
325             ,$range_end
326             );
327 2         4 push @$ref,$new_range;
328 2         4 $new_range->[key_missing]=1;
329 2         5 $new_range->[key_generated]=1;
330             }
331             }
332              
333              
334 1         4 $ranges;
335             }
336              
337             sub range_compare {
338 3     3 1 404 my ($class,$helper,$list_of_ranges,%args)=@_;
339              
340 3         10 %args=(consolidate_ranges=>1,%args);
341              
342 3 50       12 if($args{consolidate_ranges}) {
343 3         7 my $ref=[];
344 3         12 while(my $ranges=shift @$list_of_ranges) {
345 8         25 $ranges=$class->consolidate_ranges($helper,$ranges);
346 8         30 push @$ref,$ranges;
347             }
348 3         7 $list_of_ranges=$ref;
349             }
350 3         3 my ($row,$column_ids);
351 3         9 my $next=1;
352             sub {
353 14 100   14   73 return () unless $next;
354 11 100       23 if($column_ids) {
355 8         23 ($row,$column_ids,$next)=$class->compare_row(
356             $helper
357             ,$list_of_ranges
358             ,$row,$column_ids
359             );
360             } else {
361 3         13 ($row,$column_ids,$next)=$class->init_compare_row(
362             $helper
363             ,$list_of_ranges
364             );
365             }
366 11         36 @$row;
367 3         30 };
368             }
369              
370             sub init_compare_row {
371 7     7 1 31 my ($class,$helper,$data)=@_;
372              
373 7         10 my $next=0;
374 7         12 my $cols=[];
375 7         13 my $row=[];
376              
377 7         19 my @list=map { $_->[0] } @$data;
  22         47  
378 7         24 my ($first)=sort sort_smallest_range_start_first @list;
379              
380 7         35 for(my $id=0;$id<=$#$data;++$id) {
381 22         37 my $range=$data->[$id]->[0];
382 22 100       44 if($range->cmp_range_start($first)==0) {
383 13         32 push @$row,$range;
384 13         22 $cols->[$id]=0;
385 13 100       13 ++$next if $#{$data->[$id]}>0;
  13         62  
386             } else {
387 9         21 $cols->[$id]=-1;
388 9         20 push @$row,new($class,
389             $helper
390             ,$first->range_start
391             ,$range->previous_range_end
392             ,1
393             ,1
394             );
395 9         27 ++$next;
396             }
397             }
398 7         22 return $row,$cols,$next;
399             }
400              
401             sub compare_row {
402 19     19 1 2493 my ($class,$helper,$data,$row,$cols)=@_;
403              
404             # if we don't have our column list then we need to build it
405 19         57 my ($last)=sort sort_smallest_range_end_first @$row;
406             my ($end)=sort sort_largest_range_end_first
407 19         51 map { $_->[$#$_] } @$data;
  61         130  
408              
409 19         48 my $total=1 + ($#$data);
410 19         23 my $ok=$total;
411 19         24 my $missing_count=0;
412 19         60 for(my $id=0;$id<=$#$data;++$id) {
413 61         119 my $range=$row->[$id];
414              
415 61         82 my $current=$cols->[$id];
416 61         70 my $next=1 + $current;
417 61 100       62 if($#{$data->[$id]} < $next) {
  61         149  
418 37         44 $next=undef;
419             }
420            
421 61 100       125 if($last->cmp_range_end($range)==0) {
422 28 100       75 if(defined($next)) {
423 17         20 my $next_range=$data->[$id]->[$next];
424              
425 17 100       38 if($range->contiguous_check($next_range)) {
426 14         31 $cols->[$id]=$next;
427 14         26 $row->[$id]=$next_range;
428             } else {
429 3         7 $row->[$id]=new($class,
430             $helper
431             ,$range->next_range_start
432             ,$next_range->previous_range_end
433             ,1
434             ,1
435             );
436             }
437             } else {
438 11         24 $row->[$id]=new($class,
439             $helper
440             ,$range->next_range_start
441             ,$end->range_end
442             ,1
443             ,1
444             );
445             }
446             }
447 61 100       160 ++$missing_count if $row->[$id]->missing;
448 61 100       120 --$ok if $row->[$id]->cmp_range_end($end)>=0;
449             }
450 19 100 100     105 return $class->compare_row($helper,$data,$row,$cols)
451             if $ok and $missing_count==$total;
452 18         66 ($row,$cols,$ok)
453             }
454              
455             1;