File Coverage

blib/lib/AlignDB/IntSpan.pm
Criterion Covered Total %
statement 554 572 96.8
branch 145 166 87.3
condition 34 45 75.5
subroutine 90 93 96.7
pod 67 74 90.5
total 890 950 93.6


line stmt bran cond sub pod time code
1             package AlignDB::IntSpan;
2 14     14   243071 use strict;
  14         25  
  14         410  
3 14     14   57 use warnings;
  14         19  
  14         375  
4              
5 14     14   53 use Carp;
  14         24  
  14         1086  
6 14     14   7686 use Readonly;
  14         49151  
  14         783  
7 14     14   88 use Scalar::Util qw(blessed);
  14         24  
  14         1262  
8 14     14   7440 use Scalar::Util::Numeric qw(isint);
  14         9029  
  14         1439  
9              
10             use overload (
11 0     0   0 q{0+} => sub { confess "Can't numerify an AlignDB::IntSpan\n" },
12 14         123 q{bool} => q{is_not_empty},
13             q{""} => q{as_string},
14              
15             # use Perl standard behaviours for other operations
16             fallback => 1,
17 14     14   16554 );
  14         13292  
18              
19             our $VERSION = '1.1.0';
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 86 return $POS_INF - 1;
26             }
27              
28             sub NEG_INF {
29 40     40 1 99 return $NEG_INF;
30             }
31              
32             sub EMPTY_STRING {
33 973     973 1 8141 return '-';
34             }
35              
36             sub new {
37 12257     12257 1 2256536 my $class = shift;
38 12257         14257 my $self = {};
39 12257         20875 $self->{edges} = [];
40 12257         14575 bless $self, $class;
41 12257 100       25032 $self->add(@_) if @_ > 0;
42 12251         20912 return $self;
43             }
44              
45             sub valid {
46 6     6 1 5901 my $this = shift;
47 6         8 my $runlist = shift;
48              
49 6   33     31 my $class = ref($this) || $this;
50 6         10 my $set = new $class;
51              
52 6         9 eval { $set->_runlist_to_ranges($runlist) };
  6         11  
53 6 50       561 return $@ ? 0 : 1;
54             }
55              
56             sub clear {
57 2     2 1 23 my $self = shift;
58 2         5 $self->{edges} = [];
59 2         3 return $self;
60             }
61              
62             sub edges_ref {
63 231381     231381 1 176621 my $self = shift;
64 231381         317036 return $self->{edges};
65             }
66              
67             sub edges {
68 105359     105359 1 87218 my $self = shift;
69 105359         79135 return @{ $self->edges_ref };
  105359         120091  
70             }
71              
72             sub edge_size {
73 87549     87549 1 70623 my $self = shift;
74 87549         105255 return scalar $self->edges;
75             }
76              
77             sub span_size {
78 11     11 1 12 my $self = shift;
79 11         16 return $self->edge_size / 2;
80             }
81              
82             sub as_string {
83 498     498 1 8125 my $self = shift;
84              
85 498 100       766 if ( $self->is_empty ) {
86 107         165 return $self->EMPTY_STRING;
87             }
88              
89 391         338 my @runs;
90 391         500 my @edges = $self->edges;
91 391         750 while (@edges) {
92 597         613 my $lower = shift @edges;
93 597         581 my $upper = shift(@edges) - 1;
94 597 100       1693 push @runs, $lower == $upper ? $lower : "$lower-$upper";
95             }
96              
97 391         14692 return join( ',', @runs );
98             }
99              
100             sub as_array {
101 6015     6015 1 367855 my $self = shift;
102              
103 6015         5779 my @elements;
104 6015         7749 my @edges = $self->edges;
105 6015         13000 while (@edges) {
106 25836         21159 my $lower = shift @edges;
107 25836         22350 my $upper = shift(@edges) - 1;
108 25836         50299 push @elements, ( $lower .. $upper );
109             }
110              
111 6015         27220 return @elements;
112             }
113              
114             sub ranges {
115 10374     10374 1 1741641 my $self = shift;
116              
117 10374         8807 my @ranges;
118 10374         12657 my @edges = $self->edges;
119 10374         20535 while (@edges) {
120 18910         16739 my $lower = shift @edges;
121 18910         17657 my $upper = shift(@edges) - 1;
122 18910         34314 push @ranges, ( $lower, $upper );
123             }
124              
125 10374         21660 return @ranges;
126             }
127              
128             sub spans {
129 24     24 1 54 my $self = shift;
130              
131 24         19 my @spans;
132 24         38 my @edges = $self->edges;
133 24         53 while (@edges) {
134 27         29 my $lower = shift @edges;
135 27         26 my $upper = shift(@edges) - 1;
136 27         65 push @spans, [ $lower, $upper ];
137             }
138              
139 24 100       38 if (@spans) {
140 21         44 return @spans;
141             }
142             else {
143 3         20 return;
144             }
145             }
146              
147             sub sets {
148 128     128 1 143 my $self = shift;
149              
150 128         108 my @sets;
151 128         181 my @edges = $self->edges;
152 128         254 while (@edges) {
153 163         181 my $lower = shift @edges;
154 163         170 my $upper = shift(@edges) - 1;
155 163         668 push @sets, blessed($self)->new("$lower-$upper");
156             }
157              
158 128 100       190 if (@sets) {
159 125         254 return @sets;
160             }
161             else {
162 3         6 return;
163             }
164             }
165              
166             sub runlists {
167 19     19 1 43 my $self = shift;
168              
169 19 100       26 if ( $self->is_empty ) {
170 2         4 return $self->EMPTY_STRING;
171             }
172              
173 17         16 my @runlists;
174 17         21 my @edges = $self->edges;
175 17         33 while (@edges) {
176 20         19 my $lower = shift @edges;
177 20         57 my $upper = shift(@edges) - 1;
178 20 100       79 my $string = $lower == $upper ? $lower : $lower . '-' . $upper;
179 20         43 push @runlists, $string;
180             }
181              
182 17 50       26 if (@runlists) {
183 17         40 return @runlists;
184             }
185             else {
186 0         0 return;
187             }
188             }
189              
190             sub cardinality {
191 168     168 1 1294 my $self = shift;
192              
193 168         133 my $cardinality = 0;
194 168         221 my @edges = $self->edges;
195 168         319 while (@edges) {
196 226         232 my $lower = shift @edges;
197 226         219 my $upper = shift(@edges) - 1;
198 226         382 $cardinality += $upper - $lower + 1;
199             }
200              
201 168         468 return $cardinality;
202             }
203              
204             sub is_empty {
205 6404     6404 1 5781 my $self = shift;
206 6404 100       8393 my $result = $self->edge_size == 0 ? 1 : 0;
207 6404         11260 return $result;
208             }
209              
210             sub is_not_empty {
211 8     8 1 24 my $self = shift;
212 8         14 return !$self->is_empty;
213             }
214              
215             sub is_neg_inf {
216 19     19 1 36 my $self = shift;
217 19         26 return $self->edges_ref->[0] == $NEG_INF;
218             }
219              
220             sub is_pos_inf {
221 14     14 1 449 my $self = shift;
222 14         16 return $self->edges_ref->[-1] == $POS_INF;
223             }
224              
225             sub is_infinite {
226 2     2 1 2 my $self = shift;
227 2   33     3 return $self->is_neg_inf || $self->is_pos_inf;
228             }
229              
230             sub is_finite {
231 1     1 1 8 my $self = shift;
232 1         3 return !$self->is_infinite;
233             }
234              
235             sub is_universal {
236 13     13 1 11 my $self = shift;
237 13   33     21 return $self->edge_size == 2 && $self->is_neg_inf && $self->is_pos_inf;
238             }
239              
240             sub contains_all {
241 201     201 1 147 my $self = shift;
242              
243 201         176 for my $i (@_) {
244 201         221 my $pos = $self->_find_pos( $i + 1, 0 );
245 201 100       384 return 0 unless $pos & 1;
246             }
247              
248 100         105 return 1;
249             }
250              
251             sub contains_any {
252 3     3 1 8 my $self = shift;
253              
254 3         7 for my $i (@_) {
255 7         10 my $pos = $self->_find_pos( $i + 1, 0 );
256 7 100       17 return 1 if $pos & 1;
257             }
258              
259 2         8 return 0;
260             }
261              
262             sub add_pair {
263 40156     40156 1 33188 my $self = shift;
264 40156         49723 my @ranges = @_;
265              
266 40156 50       65316 if ( scalar(@ranges) != 2 ) {
267 0         0 confess "Number of ranges must be two: @ranges\n";
268             }
269              
270 40156         53778 my $edges_ref = $self->edges_ref;
271              
272 40156         38484 my $from = shift @ranges;
273 40156         41097 my $to = shift(@ranges) + 1;
274 40156 50       63070 if ( $from > $to ) {
275 0         0 confess "Bad order: $from-$to\n";
276             }
277 40156         56087 my $from_pos = $self->_find_pos( $from, 0 );
278 40156         60742 my $to_pos = $self->_find_pos( $to + 1, $from_pos );
279              
280 40156 100       66392 if ( $from_pos & 1 ) {
281 9931         13508 $from = $edges_ref->[ --$from_pos ];
282             }
283 40156 100       59415 if ( $to_pos & 1 ) {
284 7289         9594 $to = $edges_ref->[ $to_pos++ ];
285             }
286              
287 40156         34024 splice @{$edges_ref}, $from_pos, $to_pos - $from_pos, ( $from, $to );
  40156         79924  
288              
289 40156         86804 return $self;
290             }
291              
292             sub add_range {
293 31307     31307 1 734374 my $self = shift;
294 31307         40093 my @ranges = @_;
295              
296 31307 50       55760 if ( scalar(@ranges) % 2 == 1 ) {
297 0         0 confess "Number of ranges must be even: @ranges\n";
298             }
299              
300 31307         50295 while (@ranges) {
301 39278         40861 my $from = shift @ranges;
302 39278         35251 my $to = shift @ranges;
303 39278         55916 $self->add_pair( $from, $to );
304             }
305              
306 31307         36103 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 8120     8120 1 15753 my $self = shift;
320 8120         6404 my $first = shift;
321              
322 8120 100       15778 if ( ref $first eq __PACKAGE__ ) {
    100          
323 6447         8083 $self->add_range( $first->ranges );
324             }
325             elsif ( isint($first) ) {
326 815 100       1175 if ( scalar @_ > 0 ) {
327 41         78 $self->add_range( $self->_list_to_ranges( $first, @_ ) );
328             }
329             else {
330 774         1096 $self->add_pair( $first, $first );
331             }
332             }
333             else {
334 858         1433 $self->add_range( $self->_runlist_to_ranges($first) );
335             }
336              
337 8114         9535 return $self;
338             }
339              
340             sub invert {
341 5180     5180 1 5449 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 5180         7442 my $edges_ref = $self->edges_ref;
346              
347 5180 100       8594 if ( $self->is_empty ) {
348 41         277 $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       14665 if ( $edges_ref->[0] == $NEG_INF ) {
355 2536         8664 shift @{$edges_ref};
  2536         2906  
356             }
357             else {
358 2603         10459 unshift @{$edges_ref}, $NEG_INF;
  2603         8354  
359             }
360              
361 5139 100       15290 if ( $edges_ref->[-1] == $POS_INF ) {
362 2536         7418 pop @{$edges_ref};
  2536         3067  
363             }
364             else {
365 2603         7834 push @{$edges_ref}, $POS_INF;
  2603         4663  
366             }
367             }
368              
369 5180         11850 return $self;
370             }
371              
372             sub remove_range {
373 2429     2429 1 720162 my $self = shift;
374              
375 2429         4769 $self->invert;
376 2429         4370 $self->add_range(@_);
377 2429         3695 $self->invert;
378              
379 2429         3195 return $self;
380             }
381              
382             sub remove {
383 861     861 1 2333 my $self = shift;
384 861         758 my $first = shift;
385              
386 861 50       1645 if ( ref $first eq __PACKAGE__ ) {
    0          
387 861         1204 $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         2206 return $self;
402             }
403              
404             sub merge {
405 167     167 1 1131 my $self = shift;
406              
407 167         226 for my $supplied (@_) {
408 173         271 my @ranges = $self->_real_set($supplied)->ranges;
409 173         326 $self->add_range(@ranges);
410             }
411              
412 167         421 return $self;
413             }
414              
415             sub subtract {
416 141     141 1 121 my $self = shift;
417 141 50       168 return $self if $self->is_empty;
418              
419 141         217 for my $supplied (@_) {
420 141         187 my @ranges = $self->_real_set($supplied)->ranges;
421 141         257 $self->remove_range(@ranges);
422             }
423              
424 141         159 return $self;
425             }
426              
427             #@returns AlignDB::IntSpan
428             sub copy {
429 419     419 1 393 my $self = shift;
430              
431 419         1355 my $copy = blessed($self)->new;
432 419         625 $copy->{edges} = [ $self->edges ];
433              
434 419         660 return $copy;
435             }
436              
437             #@returns AlignDB::IntSpan
438             sub union {
439 33     33 1 1027 my $self = shift;
440              
441 33         50 my $new = $self->copy;
442 33         63 $new->merge(@_);
443              
444 33         67 return $new;
445             }
446              
447             #@returns AlignDB::IntSpan
448             sub complement {
449 224     224 1 205 my $self = shift;
450              
451 224         294 my $new = $self->copy;
452 224         307 $new->invert;
453              
454 224         231 return $new;
455             }
456              
457             #@returns AlignDB::IntSpan
458             sub diff {
459 167     167 1 201 my $self = shift;
460              
461 167 100       246 return $self if $self->is_empty;
462              
463 141         221 my $new = $self->copy;
464 141         231 $new->subtract(@_);
465              
466 141         220 return $new;
467             }
468              
469             #@returns AlignDB::IntSpan
470             sub intersect {
471 102     102 1 115 my $self = shift;
472              
473 102 100       156 return $self if $self->is_empty;
474              
475 98         150 my $new = $self->complement;
476 98         160 for my $supplied (@_) {
477 102         155 my $temp_set = $self->_real_set($supplied)->complement;
478 102         163 $new->merge($temp_set);
479             }
480 98         146 $new->invert;
481              
482 98         174 return $new;
483             }
484              
485             #@method
486             #@returns AlignDB::IntSpan
487             sub xor {
488 10     10 1 42 return intersect( union(@_), intersect(@_)->complement );
489             }
490              
491             sub equal {
492 115     115 1 410 my $self = shift;
493              
494 115         214 for (@_) {
495 128         197 my $supplied = $self->_real_set($_);
496              
497 128 100       180 if ( $self->edge_size != $supplied->edge_size ) {
498 40         93 return 0;
499             }
500              
501 88         130 my @edges_a = $self->edges;
502 88         127 my @edges_b = $supplied->edges;
503              
504 88         140 for ( my $i = 0; $i < $self->edge_size; $i++ ) {
505 268 100       526 if ( $edges_a[$i] != $edges_b[$i] ) {
506 18         53 return 0;
507             }
508             }
509             }
510              
511 57         142 return 1;
512             }
513              
514             sub subset {
515 74     74 1 144 my $self = shift;
516 74         110 my $supplied = $self->_real_set(shift);
517              
518 74         131 return $self->diff($supplied)->is_empty;
519             }
520              
521             sub superset {
522 74     74 1 341 my $self = shift;
523 74         109 my $supplied = $self->_real_set(shift);
524              
525 74         103 return $supplied->diff($self)->is_empty;
526             }
527              
528             sub smaller_than {
529 36     36 1 88 my $self = shift;
530 36         29 my $supplied = shift;
531              
532 36   100     83 my $result = $self->subset($supplied) && !$self->equal($supplied);
533              
534 36 100       98 return $result ? 1 : 0;
535             }
536              
537             sub larger_than {
538 36     36 1 89 my $self = shift;
539 36         30 my $supplied = shift;
540              
541 36   100     50 my $result = $self->superset($supplied) && !$self->equal($supplied);
542              
543 36 100       105 return $result ? 1 : 0;
544             }
545              
546             sub at {
547 21     21 1 50 my $self = shift;
548 21         19 my $index = shift;
549 21 100 66     66 if ( $index == 0 || abs($index) > $self->cardinality ) {
550 8         30 return;
551             }
552 13 100       33 my $member = $index < 0 ? $self->_at_neg( -$index ) : $self->_at_pos($index);
553 13         39 return $member;
554             }
555              
556             sub _at_pos {
557 6     6   13 my $self = shift;
558 6         8 my $index = shift;
559              
560 6         5 my $member;
561 6         8 my $element_before = 0;
562              
563 6         9 my @edges = $self->edges;
564 6         13 while (@edges) {
565 9         10 my $lower = shift @edges;
566 9         12 my $upper = shift(@edges) - 1;
567 9         8 my $span_size = $upper - $lower + 1;
568              
569 9 100       16 if ( $index > $element_before + $span_size ) {
570 3         5 $element_before += $span_size;
571             }
572             else {
573 6         8 $member = $index - $element_before - 1 + $lower;
574 6         10 last;
575             }
576             }
577              
578 6         8 return $member;
579             }
580              
581             sub _at_neg {
582 7     7   8 my $self = shift;
583 7         7 my $index = shift;
584              
585 7         7 my $member;
586 7         7 my $element_after = 0;
587              
588 7         10 my @r_edges = reverse $self->edges;
589 7         13 while (@r_edges) {
590 12         12 my $upper = shift(@r_edges) - 1;
591 12         12 my $lower = shift @r_edges;
592 12         13 my $span_size = $upper - $lower + 1;
593              
594 12 100       18 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         10 last;
600             }
601             }
602              
603 7         9 return $member;
604             }
605              
606             sub index {
607 11     11 1 29 my $self = shift;
608 11         10 my $member = shift;
609              
610 11         10 my $index;
611 11         10 my $element_before = 0;
612              
613 11         16 my @edges = $self->edges;
614 11         24 while (@edges) {
615 16         16 my $lower = shift @edges;
616 16         15 my $upper = shift(@edges) - 1;
617 16         16 my $span_size = $upper - $lower + 1;
618              
619 16 100 100     59 if ( $member >= $lower and $member <= $upper ) {
620 8         9 $index = $member - $lower + 1 + $element_before;
621 8         9 last;
622             }
623             else {
624 8         14 $element_before += $span_size;
625             }
626             }
627              
628 11         37 return $index;
629             }
630              
631             #@returns AlignDB::IntSpan
632             sub slice {
633 11     11 1 32 my $self = shift;
634 11         11 my $from = shift;
635 11         10 my $to = shift;
636              
637 11 100       20 if ( $from < 1 ) {
638 2         264 carp "Start index less than 1\n";
639 2         122 $from = 1;
640             }
641 11         22 my $slice = $self->_splice( $from, $to - $from + 1 );
642              
643 11         21 return $slice;
644             }
645              
646             sub _splice {
647 56     56   138 my $self = shift;
648 56         73 my $offset = shift;
649 56         49 my $length = shift;
650              
651             #@type AlignDB::IntSpan
652 56         206 my $slice = blessed($self)->new;
653              
654 56         87 my @edges = $self->edges;
655              
656 56         150 while ( @edges > 1 ) {
657 75         100 my ( $lower, $upper ) = @edges[ 0, 1 ];
658 75         87 my $span_size = $upper - $lower;
659              
660 75 100       102 if ( $offset <= $span_size ) {
661 45         57 last;
662             }
663             else {
664 30         35 splice( @edges, 0, 2 );
665 30         78 $offset -= $span_size;
666             }
667             }
668              
669             @edges
670 56 100       126 or return $slice; # empty set
671              
672 45         49 $edges[0] += $offset - 1;
673              
674 45         75 my @slices = $self->_splice_length( \@edges, $length );
675 45         82 while (@slices) {
676 67         93 my $lower = shift @slices;
677 67         65 my $upper = shift(@slices) - 1;
678 67         104 $slice->add_pair( $lower, $upper );
679             }
680              
681 45         85 return $slice;
682             }
683              
684             sub _splice_length {
685 45     45   36 my $self = shift;
686 45         38 my $edges_ref = shift;
687 45         29 my $length = shift;
688              
689 45 100       98 if ( !defined $length ) {
690 8         8 return @{$edges_ref}; # everything
  8         20  
691             }
692              
693 37 100       51 if ( $length <= 0 ) {
694 2         4 return (); # empty
695             }
696              
697 35         31 my @slices;
698              
699 35         54 while ( @$edges_ref > 1 ) {
700 54         60 my ( $lower, $upper ) = @$edges_ref[ 0, 1 ];
701 54         78 my $span_size = $upper - $lower;
702              
703 54 100       63 if ( $length <= $span_size ) {
704 28         27 last;
705             }
706             else {
707 26         33 push @slices, splice( @$edges_ref, 0, 2 );
708 26         48 $length -= $span_size;
709             }
710             }
711              
712 35 100       56 if (@$edges_ref) {
713 28         30 my $lower = shift @$edges_ref;
714 28         43 push @slices, $lower, $lower + $length;
715             }
716              
717 35         64 return @slices;
718             }
719              
720             sub min {
721 63     63 1 62 my $self = shift;
722              
723 63 50       77 if ( $self->is_empty ) {
724 0         0 return;
725             }
726             else {
727 63         78 return $self->edges_ref->[0];
728             }
729             }
730              
731             sub max {
732 63     63 1 55 my $self = shift;
733              
734 63 50       77 if ( $self->is_empty ) {
735 0         0 return;
736             }
737             else {
738 63         77 return $self->edges_ref->[-1] - 1;
739             }
740             }
741              
742             sub grep_set {
743 30     30 1 148 my $self = shift;
744 30         26 my $code_ref = shift;
745              
746 30         25 my @sub_elements;
747 30         40 for ( $self->elements ) {
748 150 100       2809 if ( $code_ref->() ) {
749 60         2184 push @sub_elements, $_;
750             }
751              
752             }
753 30         575 my $sub_set = blessed($self)->new(@sub_elements);
754              
755 30         60 return $sub_set;
756             }
757              
758             sub map_set {
759 48     48 1 222 my $self = shift;
760 48         39 my $code_ref = shift;
761              
762 48         44 my @map_elements;
763 48         75 for ( $self->elements ) {
764 227         605 for my $element ( $code_ref->() ) {
765 224 50       5677 if ( defined $element ) {
766 224         526 push @map_elements, $element;
767             }
768             }
769              
770             }
771 48         302 my $map_set = blessed($self)->new(@map_elements);
772              
773 48         101 return $map_set;
774             }
775              
776             sub substr_span {
777 5     5 1 17 my $self = shift;
778 5         6 my $string = shift;
779              
780 5         6 my $sub_string = "";
781 5         9 my @spans = $self->spans;
782              
783 5         10 for (@spans) {
784 7         9 my ( $lower, $upper ) = @$_;
785 7         8 my $length = $upper - $lower + 1;
786              
787 7         14 $sub_string .= substr( $string, $lower - 1, $length );
788             }
789              
790 5         10 return $sub_string;
791             }
792              
793             #@returns AlignDB::IntSpan
794             sub banish_span {
795 6     6 1 25 my $self = shift;
796 6         5 my $start = shift;
797 6         6 my $end = shift;
798              
799 6         6 my $remove_length = $end - $start + 1;
800              
801             my $new = $self->map_set(
802             sub {
803 17 100   17   40 $_ < $start ? $_
    100          
804             : $_ > $end ? $_ - $remove_length
805             : ();
806             }
807 6         50 );
808              
809 6         22 return $new;
810             }
811              
812             #@returns AlignDB::IntSpan
813             sub cover {
814 6     6 1 24 my $self = shift;
815              
816 6         33 my $cover = blessed($self)->new;
817 6 100       15 if ( $self->is_not_empty ) {
818 5         10 $cover->add_pair( $self->min, $self->max );
819             }
820 6         14 return $cover;
821             }
822              
823             #@returns AlignDB::IntSpan
824             sub holes {
825 14     14 1 32 my $self = shift;
826              
827 14         39 my $holes = blessed($self)->new;
828              
829 14 100 66     26 if ( $self->is_empty or $self->is_universal ) {
830              
831             # empty set and universal set have no holes
832             }
833             else {
834 13         48 my $c_set = $self->complement;
835 13         21 my @ranges = $c_set->ranges;
836              
837             # Remove infinite arms of complement set
838 13 50       22 if ( $c_set->is_neg_inf ) {
839 13         48 shift @ranges;
840 13         12 shift @ranges;
841             }
842 13 50       20 if ( $c_set->is_pos_inf ) {
843 13         50 pop @ranges;
844 13         9 pop @ranges;
845             }
846 13         25 $holes->add_range(@ranges);
847             }
848              
849 14         27 return $holes;
850             }
851              
852             #@returns AlignDB::IntSpan
853             sub inset {
854 18     18 1 45 my $self = shift;
855 18         16 my $n = shift;
856              
857 18         64 my $inset = blessed($self)->new;
858 18         30 my @edges = $self->edges;
859 18         39 while (@edges) {
860 38         43 my $lower = shift @edges;
861 38         38 my $upper = shift(@edges) - 1;
862 38 100       55 if ( $lower != $self->NEG_INF ) {
863 34         125 $lower += $n;
864             }
865 38 100       97 if ( $upper != $self->POS_INF ) {
866 34         118 $upper -= $n;
867             }
868 38 100       97 $inset->add_pair( $lower, $upper )
869             if $lower <= $upper;
870             }
871              
872 18         31 return $inset;
873             }
874              
875             #@returns AlignDB::IntSpan
876             sub trim {
877 1     1 1 2 my $self = shift;
878 1         2 my $n = shift;
879 1         4 return $self->inset($n);
880             }
881              
882             #@returns AlignDB::IntSpan
883             sub pad {
884 1     1 1 2 my $self = shift;
885 1         2 my $n = shift;
886 1         3 return $self->inset( -$n );
887             }
888              
889             #@returns AlignDB::IntSpan
890             sub excise {
891 7     7 1 17 my $self = shift;
892 7         6 my $minlength = shift;
893              
894 7         25 my $set = blessed($self)->new;
895 7         18 map { $set->merge($_) } grep { $_->size >= $minlength } $self->sets;
  10         22  
  14         22  
896              
897 7         23 return $set;
898             }
899              
900             #@returns AlignDB::IntSpan
901             sub fill {
902 8     8 1 24 my $self = shift;
903 8         6 my $maxlength = shift;
904              
905 8         16 my $set = $self->copy;
906 8 50       18 if ( $maxlength > 0 ) {
907 8         14 for my $hole ( $set->holes->sets ) {
908 11 100       19 if ( $hole->size <= $maxlength ) {
909 9         23 $set->merge($hole);
910             }
911             }
912             }
913 8         23 return $set;
914             }
915              
916             sub overlap {
917 63     63 1 64 my $self = shift;
918 63         50 my $supplied = shift;
919 63         118 return $self->intersect($supplied)->size;
920             }
921              
922             sub distance {
923 28     28 1 47 my $self = shift;
924 28         26 my $supplied = shift;
925              
926 28 50 33     35 return unless $self->size and $supplied->size;
927              
928 28         44 my $overlap = $self->overlap($supplied);
929 28 100       67 return -$overlap if $overlap;
930              
931 24         20 my $min_d;
932 24         35 for my $span1 ( $self->sets ) {
933 29         42 for my $span2 ( $supplied->sets ) {
934 29         51 my $d1 = abs( $span1->min - $span2->max );
935 29         48 my $d2 = abs( $span1->max - $span2->min );
936 29 100       50 my $d = $d1 < $d2 ? $d1 : $d2;
937 29 100 100     98 if ( !defined $min_d or $d < $min_d ) {
938 27         63 $min_d = $d;
939             }
940             }
941             }
942              
943 24         51 return $min_d;
944             }
945              
946             #@returns AlignDB::IntSpan
947             sub find_islands {
948 11     11 1 66 my $self = shift;
949 11         12 my $supplied = shift;
950              
951 11         10 my $island;
952 11 100       35 if ( ref $supplied eq __PACKAGE__ ) {
    50          
953 4         9 $island = $self->_find_islands_set($supplied);
954             }
955             elsif ( isint($supplied) ) {
956 7         11 $island = $self->_find_islands_int($supplied);
957             }
958             else {
959 0         0 confess "Don't know how to deal with input to find_island\n";
960             }
961              
962 11         17 return $island;
963             }
964              
965             sub _find_islands_int {
966 7     7   8 my $self = shift;
967 7         5 my $number = shift;
968              
969 7         31 my $island = blessed($self)->new;
970              
971             # if $pos & 1, i.e. $pos is odd number, $val is in the set
972 7         15 my $pos = $self->_find_pos( $number + 1, 0 );
973 7 100       14 if ( $pos & 1 ) {
974 5         8 my @ranges = $self->ranges;
975 5         12 $island->add_range( $ranges[ $pos - 1 ], $ranges[$pos] );
976             }
977              
978 7         9 return $island;
979             }
980              
981             sub _find_islands_set {
982 4     4   4 my $self = shift;
983 4         5 my $supplied = shift;
984              
985 4         13 my $islands = blessed($self)->new;
986              
987 4 50       10 if ( $self->overlap($supplied) ) {
988 4         6 for my $subset ( $self->sets ) {
989 8 100       11 $islands->merge($subset) if $subset->overlap($supplied);
990             }
991             }
992              
993 4         9 return $islands;
994             }
995              
996             #@returns AlignDB::IntSpan
997             sub nearest_island {
998 10     10 1 57 my $self = shift;
999 10         11 my $supplied = shift;
1000              
1001 10 100       30 if ( ref $supplied eq __PACKAGE__ ) { # just OK
    50          
1002             }
1003             elsif ( isint($supplied) ) {
1004 7         27 $supplied = blessed($self)->new($supplied);
1005             }
1006             else {
1007 0         0 confess "Don't know how to deal with input to nearest_island\n";
1008             }
1009              
1010 10         29 my $island = blessed($self)->new;
1011 10         13 my $min_d;
1012 10         16 for my $s ( $self->sets ) {
1013 19         30 for my $ss ( $supplied->sets ) {
1014 19 100       28 next if $s->overlap($ss);
1015 17         40 my $d = $s->distance($ss);
1016 17 100 100     58 if ( !defined $min_d or $d <= $min_d ) {
1017 14 100 100     37 if ( defined $min_d and $d == $min_d ) {
1018 2         3 $island->merge($s);
1019             }
1020             else {
1021 12         10 $min_d = $d;
1022 12         24 $island = $s->copy;
1023             }
1024             }
1025             }
1026             }
1027              
1028 10         34 return $island;
1029             }
1030              
1031             sub at_island {
1032 13     13 1 63 my $self = shift;
1033 13         13 my $index = shift;
1034              
1035 13 100 100     46 return if $index == 0 or abs($index) > $self->span_size;
1036              
1037 8         20 my @islands = $self->sets;
1038              
1039 8 100       39 return $index < 0 ? $islands[$index] : $islands[ $index - 1 ];
1040             }
1041              
1042             #----------------------------------------------------------#
1043             # Internal methods
1044             #----------------------------------------------------------#
1045             # Converts a list of integers into pairs of ranges
1046             sub _list_to_ranges {
1047 41     41   39 my $self = shift;
1048              
1049 41         106 my @list = sort { $a <=> $b } @_;
  617         538  
1050 41         36 my @ranges;
1051 41         38 my $count = scalar @list;
1052 41         40 my $pos = 0;
1053 41         78 while ( $pos < $count ) {
1054 172         132 my $end = $pos + 1;
1055 172   100     1133 $end++ while $end < $count && $list[$end] <= $list[ $end - 1 ] + 1;
1056 172         233 push @ranges, ( $list[$pos], $list[ $end - 1 ] );
1057 172         264 $pos = $end;
1058             }
1059              
1060 41         145 return @ranges;
1061             }
1062              
1063             # Converts a runlist into pairs of ranges
1064             sub _runlist_to_ranges {
1065 864     864   784 my $self = shift;
1066              
1067 864         769 my $runlist = shift;
1068 864         2676 $runlist =~ s/\s|_//g;
1069 864 100       1326 return if $runlist eq $self->EMPTY_STRING;
1070              
1071 747         787 my @ranges;
1072              
1073 747         1857 for my $run ( split ",", $runlist ) {
1074 1165 100       4956 if ( $run =~ /^ (-?\d+) $/x ) {
    100          
1075 199         406 push @ranges, ( $1, $1 );
1076             }
1077             elsif ( $run =~ /^ (-?\d+) - (-?\d+) $/x ) {
1078 956 100       2849 confess "Bad order: $runlist\n" if $1 > $2;
1079 954         2145 push @ranges, ( $1, $2 );
1080             }
1081             else {
1082 10         1184 confess "Bad syntax: $runlist\n";
1083             }
1084             }
1085              
1086 735         2295 return @ranges;
1087             }
1088              
1089             # Converts a set specification into a set
1090             sub _real_set {
1091 692     692   559 my $self = shift;
1092 692         525 my $supplied = shift;
1093              
1094 692 100 66     2738 if ( defined $supplied and ref $supplied eq __PACKAGE__ ) {
1095 688         1235 return $supplied;
1096             }
1097             else {
1098 4         14 return blessed($self)->new($supplied);
1099             }
1100             }
1101              
1102             # Return the index of the first element >= the supplied value.
1103             #
1104             # If the supplied value is larger than any element in the list the returned
1105             # value will be equal to the size of the list.
1106             #
1107             # If $pos & 1, i.e. $pos is odd number, $val is in the set
1108             sub _find_pos {
1109 80527     80527   66623 my $self = shift;
1110 80527         63825 my $val = shift;
1111 80527         62464 my $low = shift;
1112              
1113 80527         95896 my $edges_ref = $self->edges_ref;
1114 80527         107083 my $high = $self->edge_size;
1115              
1116 80527         133141 while ( $low < $high ) {
1117 91000         102806 my $mid = int( ( $low + $high ) / 2 );
1118 91000 100       146037 if ( $val < $edges_ref->[$mid] ) {
    100          
1119 33291         56950 $high = $mid;
1120             }
1121             elsif ( $val > $edges_ref->[$mid] ) {
1122 49155         83351 $low = $mid + 1;
1123             }
1124             else {
1125 8554         12604 return $mid;
1126             }
1127             }
1128              
1129 71973         73135 return $low;
1130             }
1131              
1132             #----------------------------------------------------------#
1133             # Aliases
1134             #----------------------------------------------------------#
1135              
1136 298     298 0 1210 sub runlist { shift->as_string(@_); }
1137 78     78 0 136 sub elements { shift->as_array(@_); }
1138 146     146 0 215 sub size { shift->cardinality(@_); }
1139 0     0 0 0 sub count { shift->cardinality(@_); }
1140 201     201 0 607 sub contains { shift->contains_all(@_); }
1141 1     1 0 7 sub intersection { shift->intersect(@_); }
1142 17     17 0 43 sub equals { shift->equal(@_); }
1143              
1144             1; # Magic true value required at end of module
1145              
1146             __END__