File Coverage

blib/lib/Array/IntSpan.pm
Criterion Covered Total %
statement 202 217 93.0
branch 111 132 84.0
condition 42 48 87.5
subroutine 19 23 82.6
pod 10 18 55.5
total 384 438 87.6


line stmt bran cond sub pod time code
1             #
2             # This file is part of Array-IntSpan
3             #
4             # This software is Copyright (c) 2014 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The Artistic License 1.0
9             #
10             ##########################################################################
11             #
12             # Array::IntSpan - a Module for handling arrays using IntSpan techniques
13             #
14             # Author: Toby Everett, Dominique Dumont
15             #
16             ##########################################################################
17             # Copyright 2003-2004,2010,2014 Dominique Dumont. All rights reserved.
18             # Copyright 2000 Toby Everett. All rights reserved.
19             #
20             # This file is distributed under the Artistic License. See
21             # http://www.ActiveState.com/corporate/artistic_license.htm or
22             # the license that comes with your perl distribution.
23             #
24             # For comments, questions, bugs or general interest, feel free to
25             # contact Dominique Dumont at ddumont@cpan.org
26             # or Toby Everett at teverett@alascom.att.com
27             ##########################################################################
28              
29 8     8   178535 use strict;
  8         15  
  8         308  
30 8     8   32 use warnings ;
  8         12  
  8         17111  
31              
32             package Array::IntSpan;
33             $Array::IntSpan::VERSION = '2.003';
34              
35 29     29 0 68 sub min { my @a = sort {$a <=> $b} @_ ; return $a[0] ; }
  29         53  
  29         40  
36 25     25 0 75 sub max { my @a = sort {$b <=> $a} @_ ; return $a[0] ; }
  25         60  
  25         41  
37              
38             sub new {
39 58     58 1 31137 my $class = shift;
40              
41 58         98 my $self = [@_];
42 58         111 bless $self, $class;
43 58         115 $self->_check_structure;
44 58         119 return $self;
45             }
46              
47             #internal function
48             sub search {
49 143     143 0 5335 my ($self,$start,$end,$index) = @_ ;
50              
51             # Binary search for the first element that is *entirely* before the
52             # element to be inserted
53 143         226 while ($start < $end) {
54 258         321 my $mid = int(($start+$end)/2);
55 258 100       375 if ($self->[$mid][1] < $index) {
56 77         125 $start = $mid+1;
57             } else {
58 181         282 $end = $mid;
59             }
60             }
61 143         187 return $start ;
62             }
63              
64             # clear the range. Note the the $self ref is preserved
65             sub clear {
66 0     0 1 0 my $self = shift;
67 0         0 @$self = () ;
68             }
69              
70             sub set_range_as_string {
71 1     1 1 6 my $self = shift;
72 1         3 my $str = shift;
73              
74 1         8 $str =~ s/\s//g;
75              
76 1         5 foreach my $substr (split /,/, $str) {
77 3 100       16 my @range = $substr =~ /-/ ? split /-/,$substr : ($substr) x 2;
78 3         7 $self->set_range(@range, @_);
79             }
80             }
81              
82             sub set {
83 1     1 1 6 my $self = shift;
84 1         2 my $idx = shift;
85              
86 1         5 $self->set_range($idx, $idx, @_);
87             }
88              
89             sub set_range {
90 11     11 1 2103 my $self = shift;
91              
92             #Test that we were passed appropriate values
93 11 50 66     42 @_ == 3 or @_ == 4 or
94             croak("Array::IntSpan::set_range should be called with 3 values and an ".
95             "optional code ref.");
96 11 50       25 $_[0] <= $_[1] or
97             croak("Array::IntSpan::set_range called with bad indices: ".
98             "$_[0] and $_[1].");
99              
100 11 50 66     34 not defined $_[3] or ref($_[3]) eq 'CODE' or
101             croak("Array::IntSpan::set_range called without 4th parameter ".
102             "set as a sub ref");
103              
104 11         28 my ($offset,$length,@list) = $self -> get_splice_parms(@_) ;
105              
106             #print "splice $offset,$length,@list\n";
107 11         28 splice @$self, $offset,$length,@list ;
108              
109 11 100       54 return $length ? 1 : 0 ;
110             }
111              
112             # not well tested or documented. May be useless...
113             sub check_clobber {
114 0     0 0 0 my $self = shift;
115              
116 0         0 my @clobbered = $self->clobbered_items(@_) ;
117              
118 0         0 map {warn "will clobber @$_ with @_\n" ;} @clobbered ;
  0         0  
119              
120 0         0 return @clobbered ;
121             }
122              
123             sub get_element
124             {
125 0     0 1 0 my ($self,$idx) = @_;
126 0         0 my $ref = $self->[$idx] ;
127 0 0       0 return () unless defined $ref ;
128 0         0 return @$ref ;
129             }
130              
131             # call-back:
132             # filler (start, end)
133             # copy (start, end, payload )
134             # set (start, end, payload)
135              
136             sub get_range {
137 32     32 1 10979 my $self = shift;
138             #my($new_elem) = [@_];
139 32         46 my ($start_elem,$end_elem, $filler, $copy, $set) = @_ ;
140              
141 32 50   21   153 $copy = sub{$_[2];} unless defined $copy ;
  21         35  
142              
143 32         40 my $end_range = $#{$self};
  32         40  
144 32         34 my $range_size = @$self ; # nb of elements
145              
146             # Before we binary search, first check if we fall before the range
147 32 100 100     149 if ($end_range < 0 or $self->[$end_range][1] < $start_elem)
148             {
149 5 100       403 my @arg = ref($filler) ?
    100          
150             ([$start_elem,$end_elem,&$filler($start_elem,$end_elem)]) :
151             defined $filler ? ([@_]) : () ;
152 5 100       17 push @$self, @arg if @arg;
153 5         19 return ref($self)->new(@arg) ;
154             }
155              
156             # Before we binary search, first check if we fall after the range
157 27 100       62 if ($end_elem < $self->[0][0])
158             {
159 1 50       6 my @arg = ref($filler) ?
    50          
160             ([$start_elem,$end_elem,&$filler($start_elem,$end_elem)]) :
161             defined $filler ? ([@_]) : () ;
162 1 50       4 unshift @$self, @arg if @arg;
163 1         3 return ref($self)->new(@arg) ;
164             }
165              
166 26         59 my $start = $self->search(0, $range_size, $start_elem) ;
167 26         40 my $end = $self->search($start,$range_size, $end_elem) ;
168              
169 26         34 my $start_offset = $start_elem - $self->[$start][0] ;
170 26 100       55 my $end_offset = defined $self->[$end] ?
171             $end_elem - $self->[$end][0] : undef ;
172              
173             #print "get_range: start $start, end $end, start_offset $start_offset";
174             #print ", end_offset $end_offset" if defined $end_offset ;
175             #print "\n";
176              
177 26         24 my @extracted ;
178             my @replaced ;
179 26         21 my $length = 0;
180              
181             # handle the start
182 26 100 100     80 if (defined $filler and $start_offset < 0)
183             {
184 4         12 my $e = min ($end_elem, $self->[$start][0]-1) ;
185 4 100       10 my $new = ref($filler) ? &$filler($start_elem, $e) : $filler ;
186 4         8 my @a = ($start_elem, $e, $new) ;
187             # don't use \@a, as we don't want @extracted and @replaced to
188             # point to the same memory area. But $new must point to the same
189             # object
190 4         6 push @extracted, [ @a ] ;
191 4         7 push @replaced, [ @a ] ;
192             }
193              
194 26 100       65 if ($self->[$start][0] <= $end_elem)
195             {
196 24         50 my $s = max ($start_elem,$self->[$start][0]) ;
197 24         39 my $e = min ($end_elem, $self->[$start][1]) ;
198 24         32 my $payload = $self->[$start][2] ;
199 24 100       45 if ($self->[$start][0] < $s)
200             {
201 10         12 my $s1 = $self->[$start][0];
202 10         19 my $e1 = $s - 1 ;
203 10         17 push @replaced, [$s1, $e1 , &$copy($s1,$e1,$payload) ];
204             }
205             # must duplicate the start, end variable
206 24         38 push @extracted, [$s, $e, $payload];
207 24         29 push @replaced, [$s, $e, $payload];
208 24 100       48 if ($e < $self->[$start][1])
209             {
210 7         6 my $s3 = $e+1 ;
211 7         9 my $e3 = $self->[$start][1] ;
212 7         13 push @replaced, [$s3, $e3, &$copy($s3, $e3,$payload) ] ;
213             }
214 24 50       37 &$set($s,$e, $payload) if defined $set ;
215 24         26 $length ++ ;
216             }
217              
218             # handle the middle if any
219 26 100       54 if ($start + 1 <= $end -1 )
220             {
221             #print "adding " ;
222 6         19 foreach my $idx ( $start+1 .. $end - 1)
223             {
224             #print "idx $idx," ;
225 8 100       22 if (defined $filler)
226             {
227 4         6 my $start_fill = $self->[$idx-1][1]+1 ;
228 4         6 my $end_fill = $self->[$idx][0]-1 ;
229 4 100       10 if ($start_fill <= $end_fill)
230             {
231 2 100       5 my $new = ref($filler) ? &$filler($start_fill, $end_fill)
232             : $filler ;
233 2         5 push @extracted, [$start_fill, $end_fill, $new] ;
234 2         4 push @replaced, [$start_fill, $end_fill, $new];
235             }
236             }
237 8         10 push @extracted, [@{$self->[$idx]}];
  8         15  
238 8         8 push @replaced , [@{$self->[$idx]}];
  8         13  
239 8         16 $length++ ;
240             }
241             #print "\n";
242             }
243              
244             # handle the end
245 26 100       51 if ($end > $start)
246             {
247 14 100       29 if (defined $filler)
248             {
249             # must add end element filler
250 7         13 my $start_fill = $self->[$end-1][1]+1 ;
251 7 100 100     31 my $end_fill = (not defined $end_offset or $end_offset < 0) ?
252             $end_elem : $self->[$end][0]-1 ;
253 7 100       14 if ($start_fill <= $end_fill)
254             {
255 5 100       11 my $new = ref($filler) ? &$filler($start_fill, $end_fill) :
256             $filler ;
257 5         9 push @extracted, [$start_fill, $end_fill, $new] ;
258 5         7 push @replaced, [$start_fill, $end_fill, $new];
259             }
260             }
261              
262 14 100 100     79 if (defined $end_offset and $end_offset >= 0)
263             {
264 6         14 my $payload = $self->[$end][2] ;
265 6         10 my $s = $self->[$end][0] ;
266 6         11 my @a = ($s,$end_elem, $payload) ;
267 6         11 push @extracted, [@a];
268 6         9 push @replaced , [@a];
269 6 100       17 if ($end_elem < $self->[$end][1])
270             {
271 4         6 my $s2 = $end_elem + 1 ;
272 4         6 my $e2 = $self->[$end][1] ;
273 4         12 push @replaced , [$s2, $e2, &$copy($s2,$e2,$payload)];
274             }
275 6 50       15 &$set($s,$end_elem, $payload) if defined $set ;
276 6         10 $length++ ;
277             }
278             }
279              
280 26 100       42 if (defined $filler)
281             {
282 11         28 splice (@$self, $start,$length , @replaced) ;
283             }
284              
285 26         61 my $ret = ref($self)->new(@extracted) ;
286 26         98 return $ret ;
287             }
288              
289             sub clobbered_items {
290 8     8 0 7674 my $self = shift;
291 8         12 my($range_start,$range_stop,$range_value) = @_;
292              
293 8         14 my $item = $self->get_range($range_start,$range_stop) ;
294              
295 8         20 return grep {$_->[2] ne $range_value} @$item ;
  8         25  
296             }
297              
298              
299             # call-back:
300             # set (start, end, payload)
301             sub consolidate {
302 14     14 1 2216 my ($self,$bottom,$top,$set) = @_;
303              
304 14 100 100     59 $bottom = 0 if (not defined $bottom or $bottom < 0 );
305 14 100 100     49 $top = $#$self if (not defined $top or $top > $#$self) ;
306              
307             #print "consolidate from $top to $bottom\n";
308              
309 14         32 for (my $i= $top; $i>0; $i--)
310             {
311 45 100 100     435 if ($self->[$i][2] eq $self->[$i-1][2] and
312             $self->[$i][0] == $self->[$i-1][1]+1 )
313             {
314             #print "consolidate splice ",$i-1,",2\n";
315 9         16 my ($s,$e,$p) = ($self->[$i-1][0], $self->[$i][1], $self->[$i][2]);
316 9         19 splice @$self, $i-1, 2, [$s, $e, $p] ;
317 9 100       26 $set->($s,$e,$p) if defined $set ;
318             }
319             }
320              
321             }
322              
323             sub set_consolidate_range {
324 13     13 0 899 my $self = shift;
325              
326             #Test that we were passed appropriate values
327 13 50 66     40 @_ == 3 or @_ == 5 or
328             croak("Array::IntSpan::set_range should be called with 3 values ".
329             "and 2 optional code ref.");
330 13 50       27 $_[0] <= $_[1] or
331             croak("Array::IntSpan::set_range called with bad indices: $_[0] and $_[1].");
332              
333 13 50 66     36 not defined $_[3] or ref($_[3]) eq 'CODE' or
334             croak("Array::IntSpan::set_range called without 4th parameter set as a sub ref");
335              
336 13         38 my ($offset,$length,@list) = $self -> get_splice_parms(@_[0,1,2,3]) ;
337              
338             #print "splice $offset,$length\n";
339 13         25 splice @$self, $offset,$length,@list ;
340 13         13 my $nb = @list ;
341              
342 13         40 $self->consolidate($offset - 1 , $offset+ $nb , $_[4]) ;
343              
344 13 100       86 return $length ? 1 : 0 ;#($b , $t ) ;
345              
346             }
347              
348             # get_range_list
349             # scalar context -> return a string
350             # list context => returns list of list
351              
352             sub get_range_list {
353 2     2 1 8 my ($self, %options) = @_;
354 2 100       5 if (wantarray) {
355 1         2 return map { [ @$_[0,1] ] } @$self;
  3         12  
356             }
357             else {
358 3         4 return join ', ' , map {
359 1         3 my ($a,$b) = @$_;
360 3 50       14 $a == $b ? $a
    100          
361             : $a+1==$b ? join(', ',$a,$b)
362             : join('-',$a,$b);
363             } @$self;
364             }
365             }
366              
367             # internal function
368             # call-back:
369             # copy (start, end, payload )
370             sub get_splice_parms {
371 52     52 0 21184 my $self = shift;
372 52         69 my ($start_elem,$end_elem,$value,$copy) = @_ ;
373              
374 52         50 my $end_range = $#{$self};
  52         64  
375 52         57 my $range_size = @$self ; # nb of elements
376              
377             #Before we binary search, we'll first check to see if this is an append operation
378 52 100 100     234 if ( $end_range < 0 or
379             $self->[$end_range][1] < $start_elem
380             )
381             {
382 8 50       44 return defined $value ? ( $range_size, 0, [$start_elem,$end_elem,$value]) :
383             ($range_size, 0) ;
384             }
385              
386             # Check for prepend operation
387 44 100       84 if ($end_elem < $self->[0][0] ) {
388 2 50       11 return defined $value ? ( 0 , 0, [$start_elem,$end_elem,$value]) : (0,0);
389             }
390              
391             #Binary search for the first element after the last element that is entirely
392             #before the element to be inserted (say that ten times fast)
393 42         82 my $start = $self->search(0, $range_size, $start_elem) ;
394 42         66 my $end = $self->search($start,$range_size, $end_elem) ;
395              
396 42         57 my $start_offset = $start_elem - $self->[$start][0] ;
397 42 100       74 my $end_offset = defined $self->[$end] ?
398             $end_elem - $self->[$end][0] : undef ;
399              
400             #print "get_splice_parms: start $start, end $end, start_offset $start_offset";
401             #print ", end_offset $end_offset" if defined $end_offset ;
402             #print "\n";
403              
404 42         51 my @modified = () ;
405              
406             #If we are here, we need to test for whether we need to frag the
407             #conflicting element
408 42 100       72 if ($start_offset > 0) {
409 15         20 my $item = $self->[$start][2] ;
410 15         19 my $s = $self->[$start][0] ;
411 15         20 my $e = $start_elem-1 ;
412 15 100       36 my $new = defined($copy) ? $copy->($s,$e,$item) : $item ;
413 15         61 push @modified ,[$s, $e, $new ];
414             }
415              
416 42 100       109 push @modified, [$start_elem,$end_elem,$value] if defined $value ;
417              
418             #Do a fragmentation check
419 42 100 100     194 if (defined $end_offset
      100        
420             and $end_offset >= 0
421             and $end_elem < $self->[$end][1]
422             ) {
423 11         14 my $item = $self->[$end][2] ;
424 11         12 my $s = $end_elem+1 ;
425 11         14 my $e = $self->[$end][1] ;
426 11 100       24 my $new = defined($copy) ? $copy->($s,$e,$item) : $item ;
427 11         22 push @modified , [$s, $e, $new] ;
428             }
429              
430 42 100 100     120 my $extra = (defined $end_offset and $end_offset >= 0) ? 1 : 0 ;
431              
432 42         111 return ($start, $end - $start + $extra , @modified);
433             }
434              
435             sub lookup {
436 7     7 1 338 my $self = shift;
437 7         6 my($key) = @_;
438              
439 7         9 my($start, $end) = (0, $#{$self});
  7         14  
440 7 100       20 return undef unless $end >= 0 ; # completely empty span
441              
442 6         14 while ($start < $end) {
443 11         18 my $mid = int(($start+$end)/2);
444 11 100       23 if ($self->[$mid][1] < $key) {
445 7         13 $start = $mid+1;
446             } else {
447 4         8 $end = $mid;
448             }
449             }
450 6 50 33     32 if ($self->[$start]->[0] <= $key && $self->[$start]->[1] >= $key) {
451 6         37 return $self->[$start]->[2];
452             }
453 0         0 return undef;
454             }
455              
456             sub _check_structure {
457 58     58   58 my $self = shift;
458              
459 58 100       159 return unless $#$self >= 0;
460              
461 51         111 foreach my $i (0..$#$self) {
462 122 50       80 @{$self->[$i]} == 3 or
  122         232  
463             croak("Array::IntSpan::_check_structure failed - element $i lacks 3 entries.");
464 122 50       227 $self->[$i][0] <= $self->[$i][1] or
465             croak("Array::IntSpan::_check_structure failed - element $i has bad indices.");
466 122 100       210 if ($i > 0) {
467 71 50       205 $self->[$i-1][1] < $self->[$i][0] or
468             croak("Array::IntSpan::_check_structure failed - element $i (",
469             ,$self->[$i][0],",",$self->[$i][1],
470             ") doesn't come after previous element (",
471             $self->[$i-1][0],",",$self->[$i-1][1],")");
472             }
473             }
474             }
475              
476             #The following code is courtesy of Mark Jacob-Dominus,
477             sub croak {
478 0     0 0   require Carp;
479 8     8   49 no warnings 'redefine' ;
  8         15  
  8         514  
480 0           *croak = \&Carp::croak;
481 0           goto &croak;
482             }
483              
484             1;
485              
486             __END__