File Coverage

blib/lib/AlignDB/IntSpan.pm
Criterion Covered Total %
statement 526 577 91.1
branch 138 166 83.1
condition 32 45 71.1
subroutine 84 98 85.7
pod 67 79 84.8
total 847 965 87.7


line stmt bran cond sub pod time code
1             package AlignDB::IntSpan;
2 13     13   195194 use strict;
  13         19  
  13         365  
3 13     13   55 use warnings;
  13         18  
  13         336  
4              
5 13     13   53 use Carp;
  13         23  
  13         890  
6 13     13   6269 use Readonly;
  13         39892  
  13         625  
7 13     13   72 use Scalar::Util qw(blessed);
  13         17  
  13         1030  
8 13     13   5850 use Scalar::Util::Numeric qw(isint);
  13         7289  
  13         1165  
9              
10             use overload (
11 0     0   0 q{0+} => sub { confess "Can't numerify an AlignDB::IntSpan\n" },
12 13         99 q{bool} => q{is_not_empty},
13             q{""} => q{runlist},
14              
15             # use Perl standard behaviours for other operations
16             fallback => 1,
17 13     13   13731 );
  13         11190  
18              
19             our $VERSION = '1.0.7';
20              
21             Readonly my $POS_INF => 2_147_483_647 - 1; # INT_MAX - 1
22             Readonly my $NEG_INF => ( -2_147_483_647 - 1 ) + 1; # INT_MIN + 1
23              
24             sub POS_INF {
25 40     40 1 68 return $POS_INF - 1;
26             }
27              
28             sub NEG_INF {
29 40     40 1 71 return $NEG_INF;
30             }
31              
32             sub EMPTY_STRING {
33 953     953 1 5241 return '-';
34             }
35              
36             sub new {
37 12226     12226 1 1952825 my $class = shift;
38 12226         12216 my $self = {};
39 12226         17214 $self->{edges} = [];
40 12226         11575 bless $self, $class;
41 12226 100       19989 $self->add(@_) if @_ > 0;
42 12226         16900 return $self;
43             }
44              
45             sub valid {
46 0     0 1 0 my $this = shift;
47 0         0 my $runlist = shift;
48              
49 0   0     0 my $class = ref($this) || $this;
50 0         0 my $set = new $class;
51              
52 0         0 eval { $set->_runlist_to_ranges($runlist) };
  0         0  
53 0 0       0 return $@ ? 0 : 1;
54             }
55              
56             sub clear {
57 2     2 1 19 my $self = shift;
58 2         4 $self->{edges} = [];
59 2         2 return $self;
60             }
61              
62             sub edges_ref {
63 231036     231036 1 137996 my $self = shift;
64 231036         237102 return $self->{edges};
65             }
66              
67             sub edges {
68 105210     105210 1 67250 my $self = shift;
69 105210         60534 return @{ $self->edges_ref };
  105210         87801  
70             }
71              
72             sub edge_size {
73 87417     87417 1 54280 my $self = shift;
74 87417         74892 return scalar $self->edges;
75             }
76              
77             sub span_size {
78 11     11 1 7 my $self = shift;
79 11         12 return $self->edge_size / 2;
80             }
81              
82             sub as_string {
83 492     492 1 366 my $self = shift;
84              
85 492 100       609 if ( $self->is_empty ) {
86 106         135 return $self->EMPTY_STRING;
87             }
88              
89 386         314 my @runs;
90 386         386 my @edges = $self->edges;
91 386         611 while (@edges) {
92 588         467 my $lower = shift @edges;
93 588         471 my $upper = shift(@edges) - 1;
94 588 100       1360 push @runs, $lower == $upper ? $lower : "$lower-$upper";
95             }
96              
97 386         8618 return join( ',', @runs );
98             }
99              
100             sub as_array {
101 6009     6009 1 291118 my $self = shift;
102              
103 6009         4458 my @elements;
104 6009         5903 my @edges = $self->edges;
105 6009         10747 while (@edges) {
106 25825         17329 my $lower = shift @edges;
107 25825         18214 my $upper = shift(@edges) - 1;
108 25825         40728 push @elements, ( $lower .. $upper );
109             }
110              
111 6009         22001 return @elements;
112             }
113              
114             sub ranges {
115 10374     10374 1 1519255 my $self = shift;
116              
117 10374         6949 my @ranges;
118 10374         10692 my @edges = $self->edges;
119 10374         16204 while (@edges) {
120 18867         13470 my $lower = shift @edges;
121 18867         13966 my $upper = shift(@edges) - 1;
122 18867         27475 push @ranges, ( $lower, $upper );
123             }
124              
125 10374         17281 return @ranges;
126             }
127              
128             sub spans {
129 19     19 1 40 my $self = shift;
130              
131 19         13 my @spans;
132 19         18 my @edges = $self->edges;
133 19         30 while (@edges) {
134 20         17 my $lower = shift @edges;
135 20         74 my $upper = shift(@edges) - 1;
136 20         44 push @spans, [ $lower, $upper ];
137             }
138              
139 19 100       24 if (@spans) {
140 17         33 return @spans;
141             }
142             else {
143 2         14 return;
144             }
145             }
146              
147             sub sets {
148 128     128 1 133 my $self = shift;
149              
150 128         85 my @sets;
151 128         151 my @edges = $self->edges;
152 128         207 while (@edges) {
153 163         149 my $lower = shift @edges;
154 163         151 my $upper = shift(@edges) - 1;
155 163         560 push @sets, blessed($self)->new("$lower-$upper");
156             }
157              
158 128 100       165 if (@sets) {
159 125         216 return @sets;
160             }
161             else {
162 3         7 return;
163             }
164             }
165              
166             sub runlists {
167 19     19 1 42 my $self = shift;
168              
169 19 100       24 if ( $self->is_empty ) {
170 2         4 return $self->EMPTY_STRING;
171             }
172              
173 17         14 my @runlists;
174 17         18 my @edges = $self->edges;
175 17         30 while (@edges) {
176 20         19 my $lower = shift @edges;
177 20         14 my $upper = shift(@edges) - 1;
178 20 100       37 my $string = $lower == $upper ? $lower : $lower . '-' . $upper;
179 20         31 push @runlists, $string;
180             }
181              
182 17 50       20 if (@runlists) {
183 17         33 return @runlists;
184             }
185             else {
186 0         0 return;
187             }
188             }
189              
190             sub cardinality {
191 168     168 1 1297 my $self = shift;
192              
193 168         109 my $cardinality = 0;
194 168         171 my @edges = $self->edges;
195 168         272 while (@edges) {
196 229         170 my $lower = shift @edges;
197 229         176 my $upper = shift(@edges) - 1;
198 229         323 $cardinality += $upper - $lower + 1;
199             }
200              
201 168         403 return $cardinality;
202             }
203              
204             sub is_empty {
205 6397     6397 1 4376 my $self = shift;
206 6397 100       6179 my $result = $self->edge_size == 0 ? 1 : 0;
207 6397         8481 return $result;
208             }
209              
210             sub is_not_empty {
211 8     8 1 19 my $self = shift;
212 8         12 return !$self->is_empty;
213             }
214              
215             sub is_neg_inf {
216 16     16 1 13 my $self = shift;
217 16         17 return $self->edges_ref->[0] == $NEG_INF;
218             }
219              
220             sub is_pos_inf {
221 13     13 1 6 my $self = shift;
222 13         14 return $self->edges_ref->[-1] == $POS_INF;
223             }
224              
225             sub is_infinite {
226 0     0 1 0 my $self = shift;
227 0   0     0 return $self->is_neg_inf || $self->is_pos_inf;
228             }
229              
230             sub is_finite {
231 0     0 1 0 my $self = shift;
232 0         0 return !$self->is_infinite;
233             }
234              
235             sub is_universal {
236 13     13 1 10 my $self = shift;
237 13   33     13 return $self->edge_size == 2 && $self->is_neg_inf && $self->is_pos_inf;
238             }
239              
240             sub contains_all {
241 208     208 1 156 my $self = shift;
242              
243 208         174 for my $i (@_) {
244 208         183 my $pos = $self->_find_pos( $i + 1, 0 );
245 208 100       309 return 0 unless $pos & 1;
246             }
247              
248 100         89 return 1;
249             }
250              
251             sub contains_any {
252 3     3 1 7 my $self = shift;
253              
254 3         4 for my $i (@_) {
255 7         9 my $pos = $self->_find_pos( $i + 1, 0 );
256 7 100       13 return 1 if $pos & 1;
257             }
258              
259 2         5 return 0;
260             }
261              
262             sub add_pair {
263 40090     40090 1 25635 my $self = shift;
264 40090         39351 my @ranges = @_;
265              
266 40090 50       53038 if ( scalar(@ranges) != 2 ) {
267 0         0 confess "Number of ranges must be two: @ranges\n";
268             }
269              
270 40090         39430 my $edges_ref = $self->edges_ref;
271              
272 40090         31495 my $from = shift @ranges;
273 40090         32107 my $to = shift(@ranges) + 1;
274 40090 50       48100 if ( $from > $to ) {
275 0         0 confess "Bad order: $from-$to\n";
276             }
277 40090         41372 my $from_pos = $self->_find_pos( $from, 0 );
278 40090         47521 my $to_pos = $self->_find_pos( $to + 1, $from_pos );
279              
280 40090 100       52361 if ( $from_pos & 1 ) {
281 9935         9032 $from = $edges_ref->[ --$from_pos ];
282             }
283 40090 100       44563 if ( $to_pos & 1 ) {
284 7288         6011 $to = $edges_ref->[ $to_pos++ ];
285             }
286              
287 40090         23190 splice @{$edges_ref}, $from_pos, $to_pos - $from_pos, ( $from, $to );
  40090         59526  
288              
289 40090         67026 return $self;
290             }
291              
292             sub add_range {
293 31297     31297 1 651123 my $self = shift;
294 31297         32928 my @ranges = @_;
295              
296 31297 50       45697 if ( scalar(@ranges) % 2 == 1 ) {
297 0         0 confess "Number of ranges must be even: @ranges\n";
298             }
299              
300 31297         41000 while (@ranges) {
301 39218         28478 my $from = shift @ranges;
302 39218         26083 my $to = shift @ranges;
303 39218         47040 $self->add_pair( $from, $to );
304             }
305              
306 31297         28720 return $self;
307             }
308              
309             sub add_runlist {
310 0     0 1 0 my $self = shift;
311 0         0 my $first = shift;
312              
313 0         0 $self->add_range( $self->_runlist_to_ranges($first) );
314              
315 0         0 return $self;
316             }
317              
318             sub add {
319 8098     8098 1 13283 my $self = shift;
320 8098         5554 my $first = shift;
321              
322 8098 100       12880 if ( ref $first eq __PACKAGE__ ) {
    100          
323 6447         6809 $self->add_range( $first->ranges );
324             }
325             elsif ( isint($first) ) {
326 806 100       1030 if ( scalar @_ > 0 ) {
327 38         59 $self->add_range( $self->_list_to_ranges( $first, @_ ) );
328             }
329             else {
330 768         928 $self->add_pair( $first, $first );
331             }
332             }
333             else {
334 845         1210 $self->add_range( $self->_runlist_to_ranges($first) );
335             }
336              
337 8098         7716 return $self;
338             }
339              
340             sub invert {
341 5179     5179 1 3802 my $self = shift;
342              
343             # $edges_ref is an ArrayRef, which points to the same array as the
344             # 'edges' attribute. So manipulate $edges_ref affects the attribute
345 5179         5509 my $edges_ref = $self->edges_ref;
346              
347 5179 100       6065 if ( $self->is_empty ) {
348 40         127 $self->{edges} = [ $NEG_INF, $POS_INF ]; # Universal set
349             }
350             else {
351              
352             # Either add or remove infinity from each end. The net
353             # effect is always an even number of additions and deletions
354 5139 100       11663 if ( $edges_ref->[0] == $NEG_INF ) {
355 2536         6542 shift @{$edges_ref};
  2536         2354  
356             }
357             else {
358 2603         8464 unshift @{$edges_ref}, $NEG_INF;
  2603         6078  
359             }
360              
361 5139 100       11547 if ( $edges_ref->[-1] == $POS_INF ) {
362 2536         5559 pop @{$edges_ref};
  2536         2402  
363             }
364             else {
365 2603         6351 push @{$edges_ref}, $POS_INF;
  2603         3359  
366             }
367             }
368              
369 5179         8884 return $self;
370             }
371              
372             sub remove_range {
373 2429     2429 1 539194 my $self = shift;
374              
375 2429         3587 $self->invert;
376 2429         3216 $self->add_range(@_);
377 2429         2740 $self->invert;
378              
379 2429         2340 return $self;
380             }
381              
382             sub remove {
383 861     861 1 1905 my $self = shift;
384 861         544 my $first = shift;
385              
386 861 50       1454 if ( ref $first eq __PACKAGE__ ) {
    0          
387 861         1109 $self->remove_range( $first->ranges );
388             }
389             elsif ( isint($first) ) {
390 0 0       0 if ( scalar @_ > 0 ) {
391 0         0 $self->remove_range( $self->_list_to_ranges( $first, @_ ) );
392             }
393             else {
394 0         0 $self->remove_range( $first, $first );
395             }
396             }
397             else {
398 0         0 $self->remove_range( $self->_runlist_to_ranges($first) );
399             }
400              
401 861         1817 return $self;
402             }
403              
404             sub merge {
405 167     167 1 1129 my $self = shift;
406              
407 167         192 for my $supplied (@_) {
408 173         212 my @ranges = $self->_real_set($supplied)->ranges;
409 173         263 $self->add_range(@ranges);
410             }
411              
412 167         267 return $self;
413             }
414              
415             sub subtract {
416 141     141 1 94 my $self = shift;
417 141 50       130 return $self if $self->is_empty;
418              
419 141         175 for my $supplied (@_) {
420 141         147 my @ranges = $self->_real_set($supplied)->ranges;
421 141         219 $self->remove_range(@ranges);
422             }
423              
424 141         139 return $self;
425             }
426              
427             #@returns AlignDB::IntSpan
428             sub copy {
429 418     418 1 328 my $self = shift;
430              
431 418         1137 my $copy = blessed($self)->new;
432 418         541 $copy->{edges} = [ $self->edges ];
433              
434 418         536 return $copy;
435             }
436              
437             #@returns AlignDB::IntSpan
438             sub union {
439 33     33 1 910 my $self = shift;
440              
441 33         44 my $new = $self->copy;
442 33         52 $new->merge(@_);
443              
444 33         62 return $new;
445             }
446              
447             #@returns AlignDB::IntSpan
448             sub complement {
449 223     223 1 161 my $self = shift;
450              
451 223         258 my $new = $self->copy;
452 223         262 $new->invert;
453              
454 223         197 return $new;
455             }
456              
457             #@returns AlignDB::IntSpan
458             sub diff {
459 167     167 1 155 my $self = shift;
460              
461 167 100       213 return $self if $self->is_empty;
462              
463 141         166 my $new = $self->copy;
464 141         200 $new->subtract(@_);
465              
466 141         155 return $new;
467             }
468              
469             #@returns AlignDB::IntSpan
470             sub intersect {
471 102     102 1 110 my $self = shift;
472              
473 102 100       132 return $self if $self->is_empty;
474              
475 98         131 my $new = $self->complement;
476 98         133 for my $supplied (@_) {
477 102         131 my $temp_set = $self->_real_set($supplied)->complement;
478 102         138 $new->merge($temp_set);
479             }
480 98         125 $new->invert;
481              
482 98         139 return $new;
483             }
484              
485             #@method
486             #@returns AlignDB::IntSpan
487             sub xor {
488 10     10 1 29 return intersect( union(@_), intersect(@_)->complement );
489             }
490              
491             sub equal {
492 115     115 1 350 my $self = shift;
493              
494 115         177 for (@_) {
495 128         164 my $supplied = $self->_real_set($_);
496              
497 128 100       144 if ( $self->edge_size != $supplied->edge_size ) {
498 40         77 return 0;
499             }
500              
501 88         101 my @edges_a = $self->edges;
502 88         99 my @edges_b = $supplied->edges;
503              
504 88         123 for ( my $i = 0; $i < $self->edge_size; $i++ ) {
505 268 100       468 if ( $edges_a[$i] != $edges_b[$i] ) {
506 18         37 return 0;
507             }
508             }
509             }
510              
511 57         126 return 1;
512             }
513              
514             sub subset {
515 74     74 1 100 my $self = shift;
516 74         95 my $supplied = $self->_real_set(shift);
517              
518 74         116 return $self->diff($supplied)->is_empty;
519             }
520              
521             sub superset {
522 74     74 1 376 my $self = shift;
523 74         90 my $supplied = $self->_real_set(shift);
524              
525 74         114 return $supplied->diff($self)->is_empty;
526             }
527              
528             sub smaller_than {
529 36     36 1 61 my $self = shift;
530 36         27 my $supplied = shift;
531              
532 36   100     43 my $result = $self->subset($supplied) && !$self->equal($supplied);
533              
534 36 100       80 return $result ? 1 : 0;
535             }
536              
537             sub larger_than {
538 36     36 1 74 my $self = shift;
539 36         23 my $supplied = shift;
540              
541 36   100     40 my $result = $self->superset($supplied) && !$self->equal($supplied);
542              
543 36 100       78 return $result ? 1 : 0;
544             }
545              
546             sub at {
547 21     21 1 45 my $self = shift;
548 21         18 my $index = shift;
549 21 100 66     49 if ( $index == 0 || abs($index) > $self->cardinality ) {
550 8         26 return;
551             }
552 13 100       33 my $member = $index < 0 ? $self->_at_neg( -$index ) : $self->_at_pos($index);
553 13         32 return $member;
554             }
555              
556             sub _at_pos {
557 6     6   5 my $self = shift;
558 6         5 my $index = shift;
559              
560 6         3 my $member;
561 6         5 my $element_before = 0;
562              
563 6         7 my @edges = $self->edges;
564 6         14 while (@edges) {
565 9         7 my $lower = shift @edges;
566 9         7 my $upper = shift(@edges) - 1;
567 9         7 my $span_size = $upper - $lower + 1;
568              
569 9 100       12 if ( $index > $element_before + $span_size ) {
570 3         5 $element_before += $span_size;
571             }
572             else {
573 6         4 $member = $index - $element_before - 1 + $lower;
574 6         7 last;
575             }
576             }
577              
578 6         6 return $member;
579             }
580              
581             sub _at_neg {
582 7     7   6 my $self = shift;
583 7         6 my $index = shift;
584              
585 7         5 my $member;
586 7         5 my $element_after = 0;
587              
588 7         7 my @r_edges = reverse $self->edges;
589 7         11 while (@r_edges) {
590 12         11 my $upper = shift(@r_edges) - 1;
591 12         8 my $lower = shift @r_edges;
592 12         8 my $span_size = $upper - $lower + 1;
593              
594 12 100       14 if ( $index > $element_after + $span_size ) {
595 5         8 $element_after += $span_size;
596             }
597             else {
598 7         7 $member = $upper - ( $index - $element_after ) + 1;
599 7         7 last;
600             }
601             }
602              
603 7         7 return $member;
604             }
605              
606             sub index {
607 11     11 1 26 my $self = shift;
608 11         8 my $member = shift;
609              
610 11         7 my $index;
611 11         7 my $element_before = 0;
612              
613 11         14 my @edges = $self->edges;
614 11         16 while (@edges) {
615 16         14 my $lower = shift @edges;
616 16         14 my $upper = shift(@edges) - 1;
617 16         13 my $span_size = $upper - $lower + 1;
618              
619 16 100 100     46 if ( $member >= $lower and $member <= $upper ) {
620 8         8 $index = $member - $lower + 1 + $element_before;
621 8         8 last;
622             }
623             else {
624 8         9 $element_before += $span_size;
625             }
626             }
627              
628 11         30 return $index;
629             }
630              
631             #@returns AlignDB::IntSpan
632             sub slice {
633 11     11 1 27 my $self = shift;
634 11         9 my $from = shift;
635 11         7 my $to = shift;
636              
637 11 100       16 if ( $from < 1 ) {
638 2         185 carp "Start index less than 1\n";
639 2         166 $from = 1;
640             }
641 11         20 my $slice = $self->_splice( $from, $to - $from + 1 );
642              
643 11         20 return $slice;
644             }
645              
646             sub _splice {
647 56     56   111 my $self = shift;
648 56         36 my $offset = shift;
649 56         34 my $length = shift;
650              
651 56         59 my @edges = $self->edges;
652 56         178 my $slice = blessed($self)->new;
653              
654 56         90 while ( @edges > 1 ) {
655 75         86 my ( $lower, $upper ) = @edges[ 0, 1 ];
656 75         52 my $span_size = $upper - $lower;
657              
658 75 100       73 if ( $offset <= $span_size ) {
659 45         45 last;
660             }
661             else {
662 30         31 splice( @edges, 0, 2 );
663 30         43 $offset -= $span_size;
664             }
665             }
666              
667             @edges
668 56 100       90 or return $slice; # empty set
669              
670 45         46 $edges[0] += $offset - 1;
671              
672 45         55 my @slices = $self->_splice_length( \@edges, $length );
673 45         66 while (@slices) {
674 67         47 my $lower = shift @slices;
675 67         49 my $upper = shift(@slices) - 1;
676 67         105 $slice->add_pair( $lower, $upper );
677             }
678              
679 45         71 return $slice;
680             }
681              
682             sub _splice_length {
683 45     45   56 my $self = shift;
684 45         30 my $edges_ref = shift;
685 45         27 my $length = shift;
686              
687 45 100       68 if ( !defined $length ) {
688 8         6 return @{$edges_ref}; # everything
  8         18  
689             }
690              
691 37 100       40 if ( $length <= 0 ) {
692 2         4 return (); # empty
693             }
694              
695 35         22 my @slices;
696              
697 35         48 while ( @$edges_ref > 1 ) {
698 54         47 my ( $lower, $upper ) = @$edges_ref[ 0, 1 ];
699 54         57 my $span_size = $upper - $lower;
700              
701 54 100       51 if ( $length <= $span_size ) {
702 28         26 last;
703             }
704             else {
705 26         25 push @slices, splice( @$edges_ref, 0, 2 );
706 26         44 $length -= $span_size;
707             }
708             }
709              
710 35 100       40 if (@$edges_ref) {
711 28         26 my $lower = shift @$edges_ref;
712 28         35 push @slices, $lower, $lower + $length;
713             }
714              
715 35         54 return @slices;
716             }
717              
718             sub min {
719 63     63 1 50 my $self = shift;
720              
721 63 50       69 if ( $self->is_empty ) {
722 0         0 return;
723             }
724             else {
725 63         67 return $self->edges_ref->[0];
726             }
727             }
728              
729             sub max {
730 63     63 1 43 my $self = shift;
731              
732 63 50       62 if ( $self->is_empty ) {
733 0         0 return;
734             }
735             else {
736 63         60 return $self->edges_ref->[-1] - 1;
737             }
738             }
739              
740             sub grep_set {
741 30     30 1 115 my $self = shift;
742 30         23 my $code_ref = shift;
743              
744 30         15 my @sub_elements;
745 30         38 for ( $self->elements ) {
746 150 100       1837 if ( $code_ref->() ) {
747 60         1617 push @sub_elements, $_;
748             }
749              
750             }
751 30         481 my $sub_set = blessed($self)->new(@sub_elements);
752              
753 30         47 return $sub_set;
754             }
755              
756             sub map_set {
757 42     42 1 162 my $self = shift;
758 42         28 my $code_ref = shift;
759              
760 42         31 my @map_elements;
761 42         52 for ( $self->elements ) {
762 210         525 for my $element ( $code_ref->() ) {
763 210 50       4860 if ( defined $element ) {
764 210         368 push @map_elements, $element;
765             }
766             }
767              
768             }
769 42         222 my $map_set = blessed($self)->new(@map_elements);
770              
771 42         70 return $map_set;
772             }
773              
774             sub substr_span {
775 0     0 1 0 my $self = shift;
776 0         0 my $string = shift;
777              
778 0         0 my $sub_string = "";
779 0         0 my @spans = $self->spans;
780              
781 0         0 for (@spans) {
782 0         0 my ( $lower, $upper ) = @$_;
783 0         0 my $length = $upper - $lower + 1;
784              
785 0         0 $sub_string .= substr( $string, $lower - 1, $length );
786             }
787              
788 0         0 return $sub_string;
789             }
790              
791             sub banish_span {
792 0     0 1 0 my $self = shift;
793 0         0 my $start = shift;
794 0         0 my $end = shift;
795              
796 0         0 my $remove_length = $end - $start + 1;
797              
798             my $new = $self->map_set(
799             sub {
800 0 0   0   0 $_ < $start ? $_
    0          
801             : $_ > $end ? $_ - $remove_length
802             : ();
803             }
804 0         0 );
805              
806 0         0 return $new;
807             }
808              
809             sub cover {
810 6     6 1 19 my $self = shift;
811              
812 6         29 my $cover = blessed($self)->new;
813 6 100       11 if ( $self->is_not_empty ) {
814 5         10 $cover->add_pair( $self->min, $self->max );
815             }
816 6         14 return $cover;
817             }
818              
819             sub holes {
820 14     14 1 27 my $self = shift;
821              
822 14         35 my $holes = blessed($self)->new;
823              
824 14 100 66     21 if ( $self->is_empty or $self->is_universal ) {
825              
826             # empty set and universal set have no holes
827             }
828             else {
829 13         47 my $c_set = $self->complement;
830 13         21 my @ranges = $c_set->ranges;
831              
832             # Remove infinite arms of complement set
833 13 50       18 if ( $c_set->is_neg_inf ) {
834 13         58 shift @ranges;
835 13         6 shift @ranges;
836             }
837 13 50       25 if ( $c_set->is_pos_inf ) {
838 13         40 pop @ranges;
839 13         9 pop @ranges;
840             }
841 13         26 $holes->add_range(@ranges);
842             }
843              
844 14         23 return $holes;
845             }
846              
847             sub inset {
848 18     18 1 41 my $self = shift;
849 18         17 my $n = shift;
850              
851 18         52 my $inset = blessed($self)->new;
852 18         25 my @edges = $self->edges;
853 18         32 while (@edges) {
854 38         34 my $lower = shift @edges;
855 38         33 my $upper = shift(@edges) - 1;
856 38 100       46 if ( $lower != $self->NEG_INF ) {
857 34         100 $lower += $n;
858             }
859 38 100       81 if ( $upper != $self->POS_INF ) {
860 34         92 $upper -= $n;
861             }
862 38 100       87 $inset->add_pair( $lower, $upper )
863             if $lower <= $upper;
864             }
865              
866 18         32 return $inset;
867             }
868              
869             sub trim {
870 1     1 1 1 my $self = shift;
871 1         2 my $n = shift;
872 1         3 return $self->inset($n);
873             }
874              
875             sub pad {
876 1     1 1 2 my $self = shift;
877 1         2 my $n = shift;
878 1         2 return $self->inset( -$n );
879             }
880              
881             sub excise {
882 7     7 1 15 my $self = shift;
883 7         7 my $minlength = shift;
884              
885 7         21 my $set = blessed($self)->new;
886 7         12 map { $set->merge($_) } grep { $_->size >= $minlength } $self->sets;
  10         15  
  14         15  
887              
888 7         19 return $set;
889             }
890              
891             sub fill {
892 8     8 1 19 my $self = shift;
893 8         8 my $maxlength = shift;
894              
895 8         13 my $set = $self->copy;
896 8 50       19 if ( $maxlength > 0 ) {
897 8         12 for my $hole ( $set->holes->sets ) {
898 11 100       14 if ( $hole->size <= $maxlength ) {
899 9         13 $set->merge($hole);
900             }
901             }
902             }
903 8         20 return $set;
904             }
905              
906             sub overlap {
907 63     63 1 54 my $self = shift;
908 63         49 my $supplied = shift;
909 63         83 return $self->intersect($supplied)->size;
910             }
911              
912             sub distance {
913 28     28 1 39 my $self = shift;
914 28         22 my $supplied = shift;
915              
916 28 50 33     35 return unless $self->size and $supplied->size;
917              
918 28         38 my $overlap = $self->overlap($supplied);
919 28 100       57 return -$overlap if $overlap;
920              
921 24         24 my $min_d;
922 24         31 for my $span1 ( $self->sets ) {
923 29         40 for my $span2 ( $supplied->sets ) {
924 29         43 my $d1 = abs( $span1->min - $span2->max );
925 29         40 my $d2 = abs( $span1->max - $span2->min );
926 29 100       45 my $d = $d1 < $d2 ? $d1 : $d2;
927 29 100 100     78 if ( !defined $min_d or $d < $min_d ) {
928 27         55 $min_d = $d;
929             }
930             }
931             }
932              
933 24         41 return $min_d;
934             }
935              
936             #@returns AlignDB::IntSpan
937             sub find_islands {
938 11     11 1 52 my $self = shift;
939 11         10 my $supplied = shift;
940              
941 11         4 my $island;
942 11 100       28 if ( ref $supplied eq __PACKAGE__ ) {
    50          
943 4         7 $island = $self->_find_islands_set($supplied);
944             }
945             elsif ( isint($supplied) ) {
946 7         10 $island = $self->_find_islands_int($supplied);
947             }
948             else {
949 0         0 confess "Don't know how to deal with input to find_island\n";
950             }
951              
952 11         16 return $island;
953             }
954              
955             sub _find_islands_int {
956 7     7   7 my $self = shift;
957 7         7 my $number = shift;
958              
959 7         21 my $island = blessed($self)->new;
960              
961             # if $pos & 1, i.e. $pos is odd number, $val is in the set
962 7         9 my $pos = $self->_find_pos( $number + 1, 0 );
963 7 100       13 if ( $pos & 1 ) {
964 5         10 my @ranges = $self->ranges;
965 5         9 $island->add_range( $ranges[ $pos - 1 ], $ranges[$pos] );
966             }
967              
968 7         9 return $island;
969             }
970              
971             sub _find_islands_set {
972 4     4   4 my $self = shift;
973 4         4 my $supplied = shift;
974              
975 4         13 my $islands = blessed($self)->new;
976              
977 4 50       7 if ( $self->overlap($supplied) ) {
978 4         7 for my $subset ( $self->sets ) {
979 8 100       13 $islands->merge($subset) if $subset->overlap($supplied);
980             }
981             }
982              
983 4         13 return $islands;
984             }
985              
986             #@returns AlignDB::IntSpan
987             sub nearest_island {
988 10     10 1 48 my $self = shift;
989 10         12 my $supplied = shift;
990              
991 10 100       28 if ( ref $supplied eq __PACKAGE__ ) { # just OK
    50          
992             }
993             elsif ( isint($supplied) ) {
994 7         26 $supplied = blessed($self)->new($supplied);
995             }
996             else {
997 0         0 confess "Don't know how to deal with input to nearest_island\n";
998             }
999              
1000 10         29 my $island = blessed($self)->new;
1001 10         10 my $min_d;
1002 10         18 for my $s ( $self->sets ) {
1003 19         27 for my $ss ( $supplied->sets ) {
1004 19 100       28 next if $s->overlap($ss);
1005 17         36 my $d = $s->distance($ss);
1006 17 100 100     47 if ( !defined $min_d or $d <= $min_d ) {
1007 14 100 100     32 if ( defined $min_d and $d == $min_d ) {
1008 2         5 $island->merge($s);
1009             }
1010             else {
1011 12         9 $min_d = $d;
1012 12         15 $island = $s->copy;
1013             }
1014             }
1015             }
1016             }
1017              
1018 10         26 return $island;
1019             }
1020              
1021             sub at_island {
1022 13     13 1 43 my $self = shift;
1023 13         7 my $index = shift;
1024              
1025 13 100 100     35 return if $index == 0 or abs($index) > $self->span_size;
1026              
1027 8         38 my @islands = $self->sets;
1028              
1029 8 100       20 return $index < 0 ? $islands[$index] : $islands[ $index - 1 ];
1030             }
1031              
1032             #----------------------------------------------------------#
1033             # Internal methods
1034             #----------------------------------------------------------#
1035             # Converts a list of integers into pairs of ranges
1036             sub _list_to_ranges {
1037 38     38   31 my $self = shift;
1038              
1039 38         80 my @list = sort { $a <=> $b } @_;
  602         426  
1040 38         26 my @ranges;
1041 38         31 my $count = scalar @list;
1042 38         28 my $pos = 0;
1043 38         60 while ( $pos < $count ) {
1044 168         107 my $end = $pos + 1;
1045 168   100     816 $end++ while $end < $count && $list[$end] <= $list[ $end - 1 ] + 1;
1046 168         149 push @ranges, ( $list[$pos], $list[ $end - 1 ] );
1047 168         200 $pos = $end;
1048             }
1049              
1050 38         103 return @ranges;
1051             }
1052              
1053             # Converts a runlist into pairs of ranges
1054             sub _runlist_to_ranges {
1055 845     845   642 my $self = shift;
1056              
1057 845         567 my $runlist = shift;
1058 845         2069 $runlist =~ s/\s|_//g;
1059 845 100       1073 return if $runlist eq $self->EMPTY_STRING;
1060              
1061 730         601 my @ranges;
1062              
1063 730         1486 for my $run ( split ",", $runlist ) {
1064 1138 100       3895 if ( $run =~ /^ (-?\d+) $/x ) {
    50          
1065 188         301 push @ranges, ( $1, $1 );
1066             }
1067             elsif ( $run =~ /^ (-?\d+) - (-?\d+) $/x ) {
1068 950 50       2146 confess "Bad order: $runlist\n" if $1 > $2;
1069 950         1746 push @ranges, ( $1, $2 );
1070             }
1071             else {
1072 0         0 confess "Bad syntax: $runlist\n";
1073             }
1074             }
1075              
1076 730         2143 return @ranges;
1077             }
1078              
1079             # Converts a set specification into a set
1080             sub _real_set {
1081 692     692   477 my $self = shift;
1082 692         461 my $supplied = shift;
1083              
1084 692 100 66     2233 if ( defined $supplied and ref $supplied eq __PACKAGE__ ) {
1085 688         981 return $supplied;
1086             }
1087             else {
1088 4         10 return blessed($self)->new($supplied);
1089             }
1090             }
1091              
1092             # Return the index of the first element >= the supplied value.
1093             #
1094             # If the supplied value is larger than any element in the list the returned
1095             # value will be equal to the size of the list.
1096             #
1097             # If $pos & 1, i.e. $pos is odd number, $val is in the set
1098             sub _find_pos {
1099 80402     80402   52167 my $self = shift;
1100 80402         49224 my $val = shift;
1101 80402         45245 my $low = shift;
1102              
1103 80402         74191 my $edges_ref = $self->edges_ref;
1104 80402         74022 my $high = $self->edge_size;
1105              
1106 80402         101342 while ( $low < $high ) {
1107 90663         76083 my $mid = int( ( $low + $high ) / 2 );
1108 90663 100       109505 if ( $val < $edges_ref->[$mid] ) {
    100          
1109 33065         40292 $high = $mid;
1110             }
1111             elsif ( $val > $edges_ref->[$mid] ) {
1112 49093         62772 $low = $mid + 1;
1113             }
1114             else {
1115 8505         9881 return $mid;
1116             }
1117             }
1118              
1119 71897         55819 return $low;
1120             }
1121              
1122             #----------------------------------------------------------#
1123             # Aliases
1124             #----------------------------------------------------------#
1125              
1126 433     433 0 7413 sub runlist { shift->as_string(@_); }
1127 0     0 0 0 sub run_list { shift->as_string(@_); }
1128 72     72 0 111 sub elements { shift->as_array(@_); }
1129 146     146 0 190 sub size { shift->cardinality(@_); }
1130 0     0 0 0 sub count { shift->cardinality(@_); }
1131 0     0 0 0 sub empty { shift->is_empty; }
1132 208     208 0 498 sub contains { shift->contains_all(@_); }
1133 0     0 0 0 sub contain { shift->contains_all(@_); }
1134 0     0 0 0 sub member { shift->contains_all(@_); }
1135 0     0 0 0 sub duplicate { shift->copy; }
1136 1     1 0 6 sub intersection { shift->intersect(@_); }
1137 17     17 0 38 sub equals { shift->equal(@_); }
1138              
1139             1; # Magic true value required at end of module
1140              
1141             __END__