File Coverage

blib/lib/Set/IntSpan/Island.pm
Criterion Covered Total %
statement 197 206 95.6
branch 79 90 87.7
condition 25 30 83.3
subroutine 26 26 100.0
pod 19 19 100.0
total 346 371 93.2


line stmt bran cond sub pod time code
1              
2             =pod
3              
4             =head1 NAME
5              
6             Set::IntSpan::Island - extension for Set::IntSpan to handle islands, holes and covers
7              
8             =head1 SYNOPSIS
9              
10             use Set::IntSpan::Island;
11              
12             # inherits normal behaviour from Set::IntSpan
13             $set = Set::IntSpan::Island->new( $set_spec );
14             # special pair input creates a span a-b
15             $set = Set::IntSpan::Island->new( $a,$b );
16              
17             # equivalent to $set->cardinality($another_set)->size;
18             if ($set->overlap( $another_set )) { ... }
19              
20             # distance between spans is negative if spans overlap, positive if not
21             $distance = $set->distance( $another_set );
22              
23             # remove islands whose size is smaller than $minsize
24             $new_set = $set->excise( $minsize );
25              
26             # remove islands whose size is found in the set $sizes_set,
27             $new_set = $set->excise( $sizes_set );
28             # all islands sized <= 10 removed
29             $new_set = $set->excise( Set::IntSpan( "(-10" ) );
30             # all islands sized >= 10 removed
31             $new_set = $set->excise( Set::IntSpan( "10-)" ) );
32             # all islands of size between 2-5 removed
33             $new_set = $set->excise( Set::IntSpan( "2-5" ) );
34              
35             # remove islands larger than $maxlength
36             $set = $set->excise_large( $minlength );
37              
38             # fill holes up to $maxsize
39             $set = $set->fill( $maxsize );
40              
41             # fill holes whose size is found in the set $sizes_set
42             $set = $set->fill( $sizes_set);
43             # all holes sizes <= 10 filled
44             $set = $set->fill( Set::IntSpan( "(-10" ) );
45             # all holes sizes >= 10 filled
46             $set = $set->fill( Set::IntSpan( "10-)" ) );
47             # all holes sizes 2-5 filled
48             $set = $set->fill( Set::IntSpan( "2-5" ) );
49              
50             # return a set composed of islands of $set that overlap $another_set
51             $set = $set->find_island( $another_set );
52              
53             # return a set composed of the nearest non-overlapping island(s) to $another_set
54             $set = $set->nearest_island( $another_set );
55              
56             # construct a list of covers by exhaustively intersecting all sets
57             @covers = Set::IntSpan::Island->extract_covers( { id1=>$set1, id2=>set2, ... } );
58             for $cover (@covers) {
59             ($coverset,@ids) = ($cover->[0], @{$cover->[1]});
60             print "cover",$coverset->run_list,"contains sets",join(",",@ids);
61             }
62              
63             =head1 DESCRIPTION
64              
65             This module extends the C module by Steve McDougall. It
66             implementing methods that are specific to islands, holes and
67             covers. C inherits from Set::IntSpan.
68              
69             =head2 Terminology
70              
71             An integer set, as represented by C, is a collection of
72             islands (or spans) on the number line
73              
74             ...-----xxxx----xxxxxxxx---xxxxxxxx---xx---x----....
75              
76             Holes are regions not in the set that fall between adjacent spans. For
77             example, the integer set above is composed of 5 islands and 4
78             holes. The two infinite regions on either side of the set are not
79             counted as holes within the context of this module.
80              
81             =head1 METHODS
82              
83             =cut
84              
85             package Set::IntSpan::Island;
86              
87 15     15   368147 use 5;
  15         58  
  15         698  
88 15     15   88 use strict;
  15         31  
  15         697  
89 15     15   78 use warnings FATAL=>"all";
  15         23  
  15         4008  
90              
91 15     15   14801 use parent qw(Exporter);
  15         5286  
  15         82  
92 15     15   779 use parent qw(Set::IntSpan);
  15         29  
  15         61  
93              
94             our @EXPORT = qw();
95             our @EXPORT_OK = qw();
96              
97 15     15   228985 use Set::IntSpan 1.13;
  15         472  
  15         800  
98 15     15   86 use Carp;
  15         33  
  15         40404  
99              
100             our $VERSION = '0.10';
101              
102             =pod
103              
104             =head2 $set = Set::IntSpan::Island->new( $set_spec )
105              
106             Constructs a set using the set specification as supported by C.
107              
108             =head2 $set = Set::IntSpan::Island->new( $a, $b )
109              
110             Extension to C C method, this double-argument
111             version creates a set formed by the range a-b. This is equivalent to
112              
113             $set = Set::IntSpan::Island->new("$a-$b")
114              
115             but permits initialization from a list instead of a string. The
116             arguments $a and $b are expected to be integers - any decimal
117             component will be truncated.
118              
119             new(1.2,2.9) equivalent to new(1,2)
120              
121             =cut
122              
123             sub new {
124 472359     472359 1 14296282 my ($this, @args) = @_;
125 472359   66     1107576 my $class = ref($this) || $this;
126 472359         529566 my $self;
127 472359 100       848463 if(@args <= 1) {
    50          
128             # relegate to parent
129 450683         1311611 $self = $class->SUPER::new(@args);
130             } elsif (@args==2) {
131             # treat as request to create span x-y
132 21676         35529 my ($x,$y) = map {int($_)} @args;
  43352         96775  
133 21676 100       50114 if($x == $y) {
134 1252         4330 $self = $class->SUPER::new($x);
135             } else {
136 20424         106908 $self = $class->SUPER::new("$x-$y");
137             }
138             } else {
139 0         0 confess "Set::IntSpan::Island: cannot create object using more than two integers [@args]";
140             }
141 472359         14494031 return $self;
142             }
143              
144             =pod
145              
146             =head2 $set_copy = $set->clone()
147              
148             Creates a copy of C<$set>. Also accessible using C<$set->duplicate()>;
149              
150             =head2 $set_copy = $set->duplicate()
151              
152             Same as C.
153              
154             =cut
155              
156             sub duplicate {
157 21     21 1 40 my $self = shift;
158 21         64 return $self->new($self->run_list);
159             }
160              
161             sub clone {
162 7     7 1 27 my $self = shift;
163 7         20 return $self->new($self->run_list);
164             }
165              
166             =pod
167              
168             =head2 $olap = $set->overlap( $another_set );
169              
170             Returns the size of intersection of two sets. Equivalent to
171              
172             $set->intersect( $another_set )->size;
173              
174             The returned value is either 0 (if the sets do not overlap) or positive (if they do).
175              
176             =cut
177              
178             sub overlap {
179 240702     240702 1 295955 my ($self,$set) = @_;
180 240702         583698 return $self->intersect($set)->size;
181             }
182              
183             =pod
184              
185             =head2 $d = $set->distance( $another_set )
186              
187             Returns the distance between sets, measured as follows. If the sets
188             overlap, then the distance is negative and given by
189              
190             $d = -$set->overlap( $another_set )
191              
192             If the sets abut, C<$d> is 1. Here $d can be interpreted as the
193             difference between the closest edges of the two sets.
194              
195             The above generalizes to 1+size(hole) if the sets do not overlap and
196             are composed of multiple islands. The hole used is the one between two
197             closest islands of the sets.
198            
199             Returns C if C<$another_set> is not defined, or either C<$set>
200             or C<$another_set> is empty.
201              
202             Here are some examples of how the distance is calculated.
203              
204             A ----xxxx---xxx-----xx--
205             B ------xxx------xx--x---
206             !! ! d=-3
207              
208             A ----xxxx---xxx-----xx--
209             B ----xxxx---xxx---------
210             !!!! !!! d=-7
211              
212             A ----xxxx---xxx-----xx--
213             B --------------x--------
214             >< d=1
215              
216             A ----xxxx---xxx-----xx--
217             B ---------------x-------
218             > < d=2
219              
220             A ----xxxx---xxx-----xx--
221             B ---------------xx------
222             > < d=2
223              
224             A ----xxxx---xxx-----xx--
225             B ---------------xxxx----
226             >< d=1
227              
228             =cut
229              
230             sub distance {
231 55     55 1 119 my ($set1,$set2) = @_;
232 55 100 100     133 return undef unless $set1 && $set2;
233 52 50 33     1473 return undef unless $set1->cardinality && $set2->cardinality;
234 52         1257 my $overlap = $set1->overlap($set2);
235 52 100       1556 if($overlap) {
236 5         13 return -$overlap;
237             } else {
238 47         51 my $min_d;
239 47         82 for my $span1 ($set1->sets) {
240 64         123 for my $span2 ($set2->sets) {
241 64         1202 my $d1 = abs($span1->min - $span2->max);
242 64         1166 my $d2 = abs($span1->max - $span2->min);
243 64 100       1030 my $d = $d1 < $d2 ? $d1 : $d2;
244 64 100 100     233 if(! defined $min_d || $d < $min_d) {
245 53         222 $min_d = $d;
246             }
247             }
248             }
249 47         182 return $min_d;
250             }
251             }
252              
253             =head2 $d = $set->sets()
254              
255             Returns all spans in $set as C objects. This method overrides the C method in C in order to return sets as Set::IntSpan::Island objects.
256              
257             =cut
258              
259             sub sets {
260 15090     15090 1 714768 my $set = shift;
261 15090         47441 return map { $set->new($_->run_list) } $set->SUPER::sets();
  64750         3587683  
262             }
263              
264             =head2 $new_set = $set->excise( $minlength | $size_set )
265              
266             Removes all islands smaller than C<$minlength>. If C<$minlength> < 1
267             then no elements are removed and a copy of the set is returned. Since
268             only islands smaller than C<$minlength> are removed, the smallest
269             useful value for C<$minlength> is 2.
270              
271             If passed a set C<$size_set>, removes all islands whose size is found
272             in C<$size_set>. This extended functionality allows you to pass in
273             arbitrary size cutoffs. For example, to remove islands of size <=10
274              
275             $new_set = $set->excise( Set::IntSpan->( "(-10" ) )
276              
277             or to remove islands of size 2-10
278              
279             $new_set = $set->excise( Set::IntSpan->( "2-10" ) )
280              
281             Since size of an island must be non-zero and positive, any negative
282             elements in the size set will be ignored. The two are therefore equivalent
283              
284             $new_set = $set->excise( Set::IntSpan->( "2-10" ) )
285             $new_set = $set->excise( Set::IntSpan->( "(--1,2-10" ) )
286              
287             Using a size set allows you to excise islands larger than a certain
288             size. For example, to remove all islands 10 or bigger,
289              
290             $new_set = $set->excise( Set::IntSpan->( "10-)" ) )
291              
292             Regardless of input, if all islands are excised (i.e. all elements
293             from $set are removed), this function will return an empty set.
294              
295             Contrast C to C. Use C when you have a set of
296             island sizes you want to remove. Use C when you have a set of
297             island sizes you want to keep. In other words, these are equivalent:
298              
299             $set->excise( $size_set )
300             $set->keep( $size_set->complement )
301              
302             Strictly speaking, you can pass in any object as a size limiter, as
303             long as it implements a C function which returns 1 if the
304             size is in the cutoff set and 0 otherwise.
305              
306             $filter = Some::Other::Module->new();
307             # set $filter parameters according to Some::Other::Module API...
308             ...
309             # $filter must implement "member" function
310             $filter->can("member")
311             if($filter->member(10)) {
312             print "islands of size 10 will be removed";
313             } else {
314             print "islands of size 10 will be kept";
315             }
316             $set->excise($filter);
317              
318             =cut
319              
320             sub excise {
321 43     43 1 460 my ($self,$length) = @_;
322 43 100       177 if(! ref($length) ) {
    50          
323 12         25 my $set = $self->new();
324 12         29 map { $set = $set->union($_) } grep($_->size >= $length, $self->sets);
  12         266  
325 12         329 return $set;
326             } elsif ($length->can("member")) {
327 31         59 my $set = $self->new();
328 31         67 map { $set = $set->union($_) } grep(! $length->member($_->size), $self->sets);
  55         2841  
329 31         1061 return $set;
330             } else {
331 0         0 confess "excise() does not accept a length cutoff of the type you used",ref($length);
332             }
333             }
334              
335             =head2 $new_set = $set->keep( $maxlength | $size_set )
336              
337             If passed an integer C<$maxlength>, removes all islands larger than
338             C<$maxlength>.
339              
340             If passed a set C<$size_set>, removes all islands whose size is not found
341             in C<$size_set>. For example, to keep all islands sized 10 or larger,
342              
343             $new_set = $set->keep( Set::IntSpan->( "10-)" ) )
344              
345             or keep all islands sized 2-10
346              
347             $new_set = $set->excise( Set::IntSpan->( "2-10" ) )
348              
349             Returns an empty set if no islands are kept.
350              
351             Since size of an island must be non-zero and positive, any negative
352             elements in the size set will be ignored. The two are therefore equivalent
353              
354             $new_set = $set->keep( Set::IntSpan->( "2-10" ) )
355             $new_set = $set->keep( Set::IntSpan->( "(--1,2-10" ) )
356              
357             Contrast C to C. Use C when you have a set of island
358             sizes you want to keep. Use C when you have a set of island
359             sizes you want to remove. In other words, these are equivalent:
360              
361             $set->keep( $size_set )
362             $set->excise( $size_set->complement )
363              
364             Strictly speaking, you can pass in any object as a size limiter, as
365             long as it implements a C function which returns 1 if the
366             size is in the cutoff set and 0 otherwise. See the description of C for details.
367              
368             =cut
369              
370             sub keep {
371 17     17 1 810 my ($self,$length) = @_;
372 17         34 my $set = $self->new();
373 17 100       80 if(! ref($length) ) {
    50          
374 5         13 map { $set = $set->union($_) } grep($_->size <= $length, $self->sets);
  2         45  
375             } elsif ($length->can("member")) {
376 12         25 map { $set = $set->union($_) } grep($length->member($_->size), $self->sets);
  11         477  
377             } else {
378 0         0 confess "keep() does not accept a length cutoff of the type you used",ref($length);
379             }
380 17         517 return $set;
381             }
382              
383             =head2 $set = $set->fill( $maxsize | $size_set )
384              
385             If passed an integer C<$maxsize>, fills in all holes in $set smaller than C<$maxsize>.
386              
387             If passed a set C<$size_set>, fills in all holes whose size appears in C<$size_set>.
388              
389             Strictly speaking, you can pass in any object as a size limiter, as
390             long as it implements a C function which returns 1 if the
391             size is in the cutoff set and 0 otherwise. See the description of C for details.
392              
393             =cut
394              
395             sub fill {
396 14     14 1 281 my ($self,$length) = @_;
397 14         31 my $set = $self->duplicate();
398 14 100       50 if(! ref($length)) {
    50          
399 10         32 for my $hole ( $set->holes->sets ) {
400 12 100       155 if($hole->size <= $length) {
401 9         129 $set = $set->union($hole);
402             }
403             }
404             } elsif ($length->can("member")) {
405 4         14 for my $hole ( $set->holes->sets ) {
406 14 100       384 if($length->member($hole->size)) {
407 11         249 $set = $set->union($hole);
408             }
409             }
410             } else {
411 0         0 confess "fill() does not accept a length cutoff of the type you used",ref($length);
412             }
413 14         530 return $set;
414             }
415              
416             =head2 $island_set = $set->find_islands( $integer | $another_set )
417              
418             Returns a set composed of islands from $set that overlap with C<$integer> or C<$another_set>.
419              
420             If an integer is passed and C<$integer> is not in C<$set>, an empty set is returned.
421              
422             If a set is passed and C<$set> and C<$another_set> have an empty intersection, an empty set is returned.
423              
424             set ----xxxx---xxx-----xx--
425             another_set ------------x----------
426             island_set -----------xxx---------
427              
428             set ----xxxx---xxx-----xx--
429             another_set ------------xxxxx------
430             island_set -----------xxx---------
431              
432             set ----xxxx---xxx-----xx--
433             another_set ------------xxxxx---xx-
434             island_set -----------xxx-----xx--
435              
436             Contrast this to nearest_island() which returns the closest island(s) that
437             do not overlap with C<$integer> or C<$another_set>.
438              
439             =cut
440              
441             sub find_islands {
442 13     13 1 294 my ($self,$anchor) = @_;
443 13 100       37 return $self->new() if ! $anchor;
444 12 100       108 if(! ref($anchor)) {
    50          
445 7         16 for my $set ($self->sets) {
446 11 100       86 return $set if $set->member($anchor);
447             }
448 2         35 return $self->new();
449             } elsif ($anchor->can("intersect")) {
450 5         13 my $islands = $self->new;
451 5 100       13 return $islands if ! $self->overlap($anchor);
452 4         221 for my $set ($self->sets) {
453 8 100       149 $islands->U($set) if $set->overlap($anchor);
454             }
455 4         114 return $islands;
456             } else {
457 0         0 confess "find_islands does not accept an argument of the type you used",ref($anchor);
458             }
459             }
460              
461             =pod
462              
463             =head2 $island_set = $set->nearest_island( $integer | $another_set)
464              
465             Returns the island(s) in C<$set> closest (but not overlapping) to
466             C<$integer> or C<$another_set>. If C<$integer> or C<$another_set> lie
467             exactly between two islands, then the returned set contains these two
468             islands.
469              
470             If no non-overlapping islands in $set are found, an empty set is returned.
471              
472             set ----xxxx---xxx-----xx--
473             another_set ------------x----------
474             island_set ----xxxx---------------
475              
476             set ----xxxx---xxx-----xx--
477             another_set ------------xxxxx------
478             island_set -------------------xx--
479              
480             set ----xxxx---xxx-----xx--
481             another_set ----------xxxxxxx------
482             island_set ----xxxx-----------xx--
483              
484             If $another_set contains multiple islands, such as below, $island_set
485             may also contain multiple islands.
486              
487             set ----xxxx---xxx-----xx--
488             another_set ---x----xxx------------
489             island_set ----xxxx---xxx---------
490              
491             Contrast this to C which returns the island(s) that
492             overlap with C<$integer> or C<$another_set>.
493              
494             =cut
495              
496             sub nearest_island {
497 21     21 1 776 my ($self,$anchor) = @_;
498 21 100       87 if(! ref($anchor)) {
    50          
499 8         16 $anchor = $self->new($anchor);
500             } elsif ($anchor->can("sets")) {
501             # same type of object
502             } else {
503 0         0 confess "nearest_island does not accept an argument of the type you used",ref($anchor);
504             }
505 21         38 my $island = $self->new();
506 21         25 my $min_d;
507 21         37 for my $s ($self->sets) {
508 38         260 for my $ss ($anchor->sets) {
509 44 100       1536 next if $s->overlap($ss);
510 34         1033 my $d = $s->distance($ss);
511 34 100 100     120 if(! defined $min_d || $d <= $min_d) {
512 26 100 100     64 if(defined $min_d && $d == $min_d) {
513 5         14 $island = $island->union($s);
514             } else {
515 21         20 $min_d = $d;
516 21         67 $island = $s;
517             }
518             }
519             }
520             }
521 21         478 return $island;
522             }
523              
524             =pod
525              
526             =head2 $num_islands = $set->num_islands()
527              
528             Returns the number of islands in the set. If the set is empty, 0 is returned.
529              
530             =cut
531              
532             sub num_islands {
533 39     39 1 119 my $self = shift;
534 39         124 return scalar $self->spans;
535             }
536              
537             =head2 $island = $set->at_island( $island_index )
538              
539             Returns the island indexed by $island_index. Islands are
540             0-indexed. For a set with N islands, the first island (ordered
541             left-to-right) has index 0 and the last island has index N-1.
542              
543             If $island_index is negative, counting is done back from the last
544             island.
545              
546             If $island_index is beyond the last island, undef is returned.
547              
548             =cut
549              
550             sub at_island {
551 140     140 1 262 my ($self,$n) = @_;
552 140         275 my @islands = $self->sets;
553 140 100 100     1401 return defined $n && defined $islands[$n] ? $islands[$n] : undef;
554             }
555              
556             =pod
557              
558             =head2 $island = $set->first_island()
559              
560             Returns the first island of the set. As a side-effect, sets the
561             iterator to the first island.
562              
563             If the set is empty, returns undef.
564              
565             =cut
566              
567             sub first_island {
568 11     11 1 55 my $self = shift;
569 11 100       40 if($self->cardinality) {
570 9         103 $self->{iterator} = 0;
571 9         20 return $self->at_island( $self->{iterator} );
572             } else {
573 2         29 $self->{iterator} = undef;
574 2         15 return undef;
575             }
576             }
577              
578             =pod
579              
580             =head2 $island = $set->last_island()
581              
582             Returns the last island of the set. As a side-effect, sets the
583             iterator to the last island.
584              
585             If the set is empty, returns undef.
586              
587             =cut
588              
589             sub last_island {
590 9     9 1 44 my $self = shift;
591 9 50       24 if($self->cardinality) {
592 9         181 $self->{iterator} = $self->num_islands - 1;
593 9         230 return $self->at_island( $self->{iterator} );
594             } else {
595 0         0 $self->{iterator} = undef;
596 0         0 return undef;
597             }
598             }
599              
600             =pod
601              
602             =head2 $island = $set->next_island()
603              
604             Advances the iterator forward by one island, and returns the next
605             island. If the iterator is undefined, the first island is returned.
606              
607             Returns undef if the set is empty or if no more islands are available.
608              
609             =cut
610              
611             sub next_island {
612 29     29 1 19520 my $self = shift;
613              
614 29 100       91 if($self->cardinality) {
615 28 100       366 $self->{iterator} = defined $self->{iterator} ? ++$self->{iterator} : 0;
616 28         72 my $next = $self->at_island( $self->{iterator} );
617 28 100       108 if($next) {
618 18         262 return $next;
619             } else {
620 10         21 $self->{iterator} = undef;
621 10         28 return undef;
622             }
623             } else {
624 1         16 $self->{iterator} = undef;
625 1         5 return undef;
626             }
627             }
628              
629             =pod
630              
631             =head2 $island = $set->prev_island()
632              
633             Reverses the iterator backward by one island, and returns the previous
634             island. If the iterator is undefined, the last island is returned.
635              
636             Returns undef if the set is empty or if no more islands are available.
637              
638             =cut
639              
640             sub prev_island {
641 29     29 1 12784 my $self = shift;
642 29 100       80 if($self->cardinality) {
643 28 100       368 $self->{iterator} = defined $self->{iterator} ? --$self->{iterator} : $self->num_islands - 1;
644 28 100       281 if($self->{iterator} >= 0) {
645 18         62 return $self->at_island( $self->{iterator} );
646             } else {
647 10         36 $self->{iterator} = undef;
648 10         27 return undef;
649             }
650             } else {
651 1         13 $self->{iterator} = undef;
652 1         3 return undef;
653             }
654             }
655              
656             =pod
657              
658             =head2 $island = $set->current_island()
659              
660             Returns the island at the current iterator position.
661              
662             Returns undef if the set is empty or if the iterator is not defined.
663              
664             =cut
665              
666             sub current_island {
667 58     58 1 5899 my $self = shift;
668 58         147 return $self->at_island( $self->{iterator} );
669             }
670              
671             =pod
672              
673             =head2 $cover_data = Set::IntSpan::Island->extract_covers( $set_hash_ref )
674              
675             Given a C<$set_hash> reference
676              
677             { id1=>$set1, id2=>$set2, ..., idn=>$setn}
678              
679             where C<$setj> is a finite C object and C
680             is a unique key, C performs an exhaustive intersection
681             of all sets and returns a list of all covers and set memberships. For
682             example, given the id/runlist combination
683              
684             a 10-15
685             b 12
686             c 14-20
687             d 25
688              
689             The covers are
690              
691             10-11 a
692             12 a b
693             13 a
694             14-15 a c
695             16-20 c
696             21-24 -
697             25 d
698              
699             The cover data is returned as an array reference and its structure is
700              
701             [ [ $cover_set1, [ id11, id12, id13, ... ] ],
702             [ $cover_set2, [ id21, id22, id23, ... ] ],
703             ...
704             ]
705              
706             If a cover contains no elements, then its entry is
707              
708             [ $cover_set, [ ] ]
709              
710             =cut
711              
712             sub extract_covers {
713 115     115 1 5901 my ($self,$sets) = @_;
714              
715 115 50 33     943 if(! $sets || ref($sets) ne "HASH") {
716 0         0 return [];
717             }
718              
719             # decompose all input sets into spans
720 115         156 my @sets;
721 115         788 for my $id (keys %$sets) {
722 5047 50       28304 confess "value in hash is not a set object" unless $sets->{$id}->can("sets");
723 5047         12829 for my $span ($sets->{$id}->sets) {
724 53832         199481 push @sets,[$id,$span];
725             }
726             }
727             # order the spans by increasing min and increasing max
728 115 50       965670 @sets = sort {$a->[1]->min <=> $b->[1]->min || $a->[1]->max <=> $b->[1]->max} @sets;
  410908         12930404  
729             # register integers at which cover set membership may change - these are the
730             # integers at set boundaries
731 115         4440 my %edges;
732 115         418 for my $set (@sets) {
733 53832         77523 map {$edges{$_}++} ( map { ($_->[1]->min-1,$_->[1]->min,$_->[1]->max,$_->[1]->max+1) } $set );
  215328         2381805  
  53832         158850  
734             }
735 115         3478 my @edges = sort {$a <=> $b} keys %edges;
  42404         42618  
736             # first and last edge are not part of any set (min(leftmost)-1, max(rightmost)+1) - remove them
737 115         818 splice(@edges,0,1);
738 115         306 splice(@edges,-1,1);
739 115         214 my $i = 0;
740 115         226 my $j_low = 0;
741 115         145 my $covers;
742             #print "edges ",join(" ",@edges),"\n";
743 115         468 while($i < @edges) {
744 5356         8610 my $edge = $edges[$i];
745 5356         8388 my $edge_next = $edges[$i+1];
746 5356         5411 my $cover;
747 5356 100 100     23789 if(! defined $edge_next || $edge + 1 == $edge_next) {
748 2671         6688 $cover = $self->new($edge);
749 2671         3927 $i++;
750             } else {
751 2685         7789 $cover = $self->new($edge,$edge_next);
752 2685         4025 $i += 2;
753             }
754             #printf("cover %3d %3d j_low %d\n",$cover->min,$cover->max,$j_low);
755 5356         6344 my $found;
756 5356         7749 my $j_low_incr = 0;
757 5356         13259 push @$covers, [ $cover , []];
758 5356         15305 for my $j ($j_low..@sets-1) {
759 240544         1771917 my ($id,$set) = @{$sets[$j]};
  240544         547653  
760 240544         542158 my $ol = $set->overlap($cover);
761 240544 100       12011893 if($ol) {
762 99046         127734 $found = 1;
763             #print " ",$sets[$j][0]," ",$set->run_list,"\n" if $ol;
764 99046         97755 push @{$covers->[-1][1]}, $id;
  99046         309115  
765             } else {
766 141498 100       227906 if($found) {
767 78063 100       176434 last if $set->min > $cover->max;
768             } else {
769 63435         111326 $j_low_incr++;
770             }
771             }
772             }
773 5356 100 100     124445 if(@$covers > 1 &&
  5241         21668  
774 5241         40085 join("",@{$covers->[-1][1]}) eq join("",@{$covers->[-2][1]})) {
775 773         4003 $covers->[-2][0] = $covers->[-2][0]->union ($covers->[-1][0]);
776 773         28313 splice(@$covers,-1,1);
777             }
778 5356 100       24423 $j_low += $j_low_incr if $found;
779             }
780 115         117521 return $covers;
781             }
782              
783             1;
784              
785             __END__