File Coverage

blib/lib/Array/IntSpan.pm
Criterion Covered Total %
statement 186 201 92.5
branch 104 124 83.8
condition 42 48 87.5
subroutine 16 20 80.0
pod 7 15 46.6
total 355 408 87.0


line stmt bran cond sub pod time code
1             ##########################################################################
2             #
3             # Array::IntSpan - a Module for handling arrays using IntSpan techniques
4             #
5             # Author: Toby Everett, Dominique Dumont
6             #
7             ##########################################################################
8             # Copyright 2003-2004,2010 Dominique Dumont. All rights reserved.
9             # Copyright 2000 Toby Everett. All rights reserved.
10             #
11             # This file is distributed under the Artistic License. See
12             # http://www.ActiveState.com/corporate/artistic_license.htm or
13             # the license that comes with your perl distribution.
14             #
15             # For comments, questions, bugs or general interest, feel free to
16             # contact Dominique Dumont at dominique.dumont@hp.com
17             # or Toby Everett at teverett@alascom.att.com
18             ##########################################################################
19              
20             # $Author: domi $
21             # $Date: 2010-09-28 09:35:55 $
22             # $Name: $
23             # $Revision: 2.2 $
24              
25              
26 8     8   376721 use strict;
  8         47  
  8         317  
27 8     8   47 use warnings ;
  8         16  
  8         23685  
28              
29             package Array::IntSpan;
30              
31             our $VERSION = sprintf "%d.%03d", q$Revision: 2.2 $ =~ /(\d+)\.(\d+)/;
32              
33 29     29 0 83 sub min { my @a = sort {$a <=> $b} @_ ; return $a[0] ; }
  29         78  
  29         62  
34 25     25 0 108 sub max { my @a = sort {$b <=> $a} @_ ; return $a[0] ; }
  25         90  
  25         54  
35              
36             sub new {
37 56     56 1 42267 my $class = shift;
38              
39 56         119 my $self = [@_];
40 56         138 bless $self, $class;
41 56         155 $self->_check_structure;
42 56         165 return $self;
43             }
44              
45             #internal function
46             sub search {
47 139     139 0 6340 my ($self,$start,$end,$index) = @_ ;
48              
49             # Binary search for the first element that is *entirely* before the
50             # element to be inserted
51 139         320 while ($start < $end) {
52 252         440 my $mid = int(($start+$end)/2);
53 252 100       639 if ($self->[$mid][1] < $index) {
54 75         194 $start = $mid+1;
55             } else {
56 177         415 $end = $mid;
57             }
58             }
59 139         285 return $start ;
60             }
61              
62             # clear the range. Note the the $self ref is preserved
63             sub clear
64             {
65 0     0 1 0 my $self = shift;
66 0         0 @$self = () ;
67             }
68              
69             sub set_range {
70 7     7 1 3229 my $self = shift;
71              
72             #Test that we were passed appropriate values
73 7 50 66     37 @_ == 3 or @_ == 4 or
74             croak("Array::IntSpan::set_range should be called with 3 values and an ".
75             "optional code ref.");
76 7 50       21 $_[0] <= $_[1] or
77             croak("Array::IntSpan::set_range called with bad indices: ".
78             "$_[0] and $_[1].");
79              
80 7 50 66     36 not defined $_[3] or ref($_[3]) eq 'CODE' or
81             croak("Array::IntSpan::set_range called without 4th parameter ".
82             "set as a sub ref");
83              
84 7         27 my ($offset,$length,@list) = $self -> get_splice_parms(@_) ;
85              
86             #print "splice $offset,$length,@list\n";
87 7         23 splice @$self, $offset,$length,@list ;
88              
89 7 100       54 return $length ? 1 : 0 ;
90             }
91              
92             # not well tested or documented. May be useless...
93             sub check_clobber {
94 0     0 0 0 my $self = shift;
95              
96 0         0 my @clobbered = $self->clobbered_items(@_) ;
97              
98 0         0 map {warn "will clobber @$_ with @_\n" ;} @clobbered ;
  0         0  
99              
100 0         0 return @clobbered ;
101             }
102              
103             sub get_element
104             {
105 0     0 1 0 my ($self,$idx) = @_;
106 0         0 my $ref = $self->[$idx] ;
107 0 0       0 return () unless defined $ref ;
108 0         0 return @$ref ;
109             }
110              
111             # call-back:
112             # filler (start, end)
113             # copy (start, end, payload )
114             # set (start, end, payload)
115              
116             sub get_range {
117 32     32 1 27600 my $self = shift;
118             #my($new_elem) = [@_];
119 32         60 my ($start_elem,$end_elem, $filler, $copy, $set) = @_ ;
120              
121 32 50   21   199 $copy = sub{$_[2];} unless defined $copy ;
  21         64  
122              
123 32         45 my $end_range = $#{$self};
  32         63  
124 32         52 my $range_size = @$self ; # nb of elements
125              
126             # Before we binary search, first check if we fall before the range
127 32 100 100     312 if ($end_range < 0 or $self->[$end_range][1] < $start_elem)
128             {
129 5 100       26 my @arg = ref($filler) ?
    100          
130             ([$start_elem,$end_elem,&$filler($start_elem,$end_elem)]) :
131             defined $filler ? ([@_]) : () ;
132 5 100       24 push @$self, @arg if @arg;
133 5         29 return ref($self)->new(@arg) ;
134             }
135              
136             # Before we binary search, first check if we fall after the range
137 27 100       97 if ($end_elem < $self->[0][0])
138             {
139 1 50       6 my @arg = ref($filler) ?
    50          
140             ([$start_elem,$end_elem,&$filler($start_elem,$end_elem)]) :
141             defined $filler ? ([@_]) : () ;
142 1 50       5 unshift @$self, @arg if @arg;
143 1         5 return ref($self)->new(@arg) ;
144             }
145              
146 26         302 my $start = $self->search(0, $range_size, $start_elem) ;
147 26         65 my $end = $self->search($start,$range_size, $end_elem) ;
148              
149 26         49 my $start_offset = $start_elem - $self->[$start][0] ;
150 26 100       74 my $end_offset = defined $self->[$end] ?
151             $end_elem - $self->[$end][0] : undef ;
152              
153             #print "get_range: start $start, end $end, start_offset $start_offset";
154             #print ", end_offset $end_offset" if defined $end_offset ;
155             #print "\n";
156              
157 26         38 my @extracted ;
158             my @replaced ;
159 26         36 my $length = 0;
160              
161             # handle the start
162 26 100 100     106 if (defined $filler and $start_offset < 0)
163             {
164 4         17 my $e = min ($end_elem, $self->[$start][0]-1) ;
165 4 100       13 my $new = ref($filler) ? &$filler($start_elem, $e) : $filler ;
166 4         10 my @a = ($start_elem, $e, $new) ;
167             # don't use \@a, as we don't want @extracted and @replaced to
168             # point to the same memory area. But $new must point to the same
169             # object
170 4         12 push @extracted, [ @a ] ;
171 4         9 push @replaced, [ @a ] ;
172             }
173              
174 26 100       73 if ($self->[$start][0] <= $end_elem)
175             {
176 24         69 my $s = max ($start_elem,$self->[$start][0]) ;
177 24         572 my $e = min ($end_elem, $self->[$start][1]) ;
178 24         50 my $payload = $self->[$start][2] ;
179 24 100       68 if ($self->[$start][0] < $s)
180             {
181 10         18 my $s1 = $self->[$start][0];
182 10         15 my $e1 = $s - 1 ;
183 10         41 push @replaced, [$s1, $e1 , &$copy($s1,$e1,$payload) ];
184             }
185             # must duplicate the start, end variable
186 24         65 push @extracted, [$s, $e, $payload];
187 24         48 push @replaced, [$s, $e, $payload];
188 24 100       78 if ($e < $self->[$start][1])
189             {
190 7         14 my $s3 = $e+1 ;
191 7         10 my $e3 = $self->[$start][1] ;
192 7         20 push @replaced, [$s3, $e3, &$copy($s3, $e3,$payload) ] ;
193             }
194 24 50       59 &$set($s,$e, $payload) if defined $set ;
195 24         39 $length ++ ;
196             }
197              
198             # handle the middle if any
199 26 100       290 if ($start + 1 <= $end -1 )
200             {
201             #print "adding " ;
202 6         19 foreach my $idx ( $start+1 .. $end - 1)
203             {
204             #print "idx $idx," ;
205 8 100       27 if (defined $filler)
206             {
207 4         7 my $start_fill = $self->[$idx-1][1]+1 ;
208 4         8 my $end_fill = $self->[$idx][0]-1 ;
209 4 100       10 if ($start_fill <= $end_fill)
210             {
211 2 100       8 my $new = ref($filler) ? &$filler($start_fill, $end_fill)
212             : $filler ;
213 2         7 push @extracted, [$start_fill, $end_fill, $new] ;
214 2         4 push @replaced, [$start_fill, $end_fill, $new];
215             }
216             }
217 8         13 push @extracted, [@{$self->[$idx]}];
  8         24  
218 8         12 push @replaced , [@{$self->[$idx]}];
  8         19  
219 8         18 $length++ ;
220             }
221             #print "\n";
222             }
223              
224             # handle the end
225 26 100       71 if ($end > $start)
226             {
227 14 100       38 if (defined $filler)
228             {
229             # must add end element filler
230 7         15 my $start_fill = $self->[$end-1][1]+1 ;
231 7 100 100     35 my $end_fill = (not defined $end_offset or $end_offset < 0) ?
232             $end_elem : $self->[$end][0]-1 ;
233 7 100       17 if ($start_fill <= $end_fill)
234             {
235 5 100       16 my $new = ref($filler) ? &$filler($start_fill, $end_fill) :
236             $filler ;
237 5         15 push @extracted, [$start_fill, $end_fill, $new] ;
238 5         11 push @replaced, [$start_fill, $end_fill, $new];
239             }
240             }
241              
242 14 100 100     74 if (defined $end_offset and $end_offset >= 0)
243             {
244 6         13 my $payload = $self->[$end][2] ;
245 6         16 my $s = $self->[$end][0] ;
246 6         15 my @a = ($s,$end_elem, $payload) ;
247 6         13 push @extracted, [@a];
248 6         14 push @replaced , [@a];
249 6 100       19 if ($end_elem < $self->[$end][1])
250             {
251 4         8 my $s2 = $end_elem + 1 ;
252 4         10 my $e2 = $self->[$end][1] ;
253 4         10 push @replaced , [$s2, $e2, &$copy($s2,$e2,$payload)];
254             }
255 6 50       17 &$set($s,$end_elem, $payload) if defined $set ;
256 6         14 $length++ ;
257             }
258             }
259              
260 26 100       90 if (defined $filler)
261             {
262 11         37 splice (@$self, $start,$length , @replaced) ;
263             }
264              
265 26         97 my $ret = ref($self)->new(@extracted) ;
266 26         152 return $ret ;
267             }
268              
269             sub clobbered_items {
270 8     8 0 17554 my $self = shift;
271 8         18 my($range_start,$range_stop,$range_value) = @_;
272              
273 8         26 my $item = $self->get_range($range_start,$range_stop) ;
274              
275 8         29 return grep {$_->[2] ne $range_value} @$item ;
  8         39  
276             }
277              
278              
279             # call-back:
280             # set (start, end, payload)
281             sub consolidate {
282 14     14 1 3781 my ($self,$bottom,$top,$set) = @_;
283              
284 14 100 100     72 $bottom = 0 if (not defined $bottom or $bottom < 0 );
285 14 100 100     66 $top = $#$self if (not defined $top or $top > $#$self) ;
286              
287             #print "consolidate from $top to $bottom\n";
288              
289 14         41 for (my $i= $top; $i>0; $i--)
290             {
291 45 100 100     623 if ($self->[$i][2] eq $self->[$i-1][2] and
292             $self->[$i][0] == $self->[$i-1][1]+1 )
293             {
294             #print "consolidate splice ",$i-1,",2\n";
295 9         24 my ($s,$e,$p) = ($self->[$i-1][0], $self->[$i][1], $self->[$i][2]);
296 9         29 splice @$self, $i-1, 2, [$s, $e, $p] ;
297 9 100       40 $set->($s,$e,$p) if defined $set ;
298             }
299             }
300              
301             }
302              
303             sub set_consolidate_range {
304 13     13 0 1509 my $self = shift;
305              
306             #Test that we were passed appropriate values
307 13 50 66     51 @_ == 3 or @_ == 5 or
308             croak("Array::IntSpan::set_range should be called with 3 values ".
309             "and 2 optional code ref.");
310 13 50       34 $_[0] <= $_[1] or
311             croak("Array::IntSpan::set_range called with bad indices: $_[0] and $_[1].");
312              
313 13 50 66     47 not defined $_[3] or ref($_[3]) eq 'CODE' or
314             croak("Array::IntSpan::set_range called without 4th parameter set as a sub ref");
315              
316 13         53 my ($offset,$length,@list) = $self -> get_splice_parms(@_[0,1,2,3]) ;
317              
318             #print "splice $offset,$length\n";
319 13         35 splice @$self, $offset,$length,@list ;
320 13         19 my $nb = @list ;
321              
322 13         54 $self->consolidate($offset - 1 , $offset+ $nb , $_[4]) ;
323              
324 13 100       119 return $length ? 1 : 0 ;#($b , $t ) ;
325              
326             }
327              
328             # internal function
329             # call-back:
330             # copy (start, end, payload )
331             sub get_splice_parms {
332 48     48 0 36485 my $self = shift;
333 48         102 my ($start_elem,$end_elem,$value,$copy) = @_ ;
334              
335 48         66 my $end_range = $#{$self};
  48         82  
336 48         78 my $range_size = @$self ; # nb of elements
337              
338             #Before we binary search, we'll first check to see if this is an append operation
339 48 100 100     303 if ( $end_range < 0 or
340             $self->[$end_range][1] < $start_elem
341             )
342             {
343 7 50       72 return defined $value ? ( $range_size, 0, [$start_elem,$end_elem,$value]) :
344             ($range_size, 0) ;
345             }
346              
347             # Check for prepend operation
348 41 100       119 if ($end_elem < $self->[0][0] ) {
349 1 50       8 return defined $value ? ( 0 , 0, [$start_elem,$end_elem,$value]) : (0,0);
350             }
351              
352             #Binary search for the first element after the last element that is entirely
353             #before the element to be inserted (say that ten times fast)
354 40         127 my $start = $self->search(0, $range_size, $start_elem) ;
355 40         93 my $end = $self->search($start,$range_size, $end_elem) ;
356              
357 40         80 my $start_offset = $start_elem - $self->[$start][0] ;
358 40 100       106 my $end_offset = defined $self->[$end] ?
359             $end_elem - $self->[$end][0] : undef ;
360              
361             #print "get_splice_parms: start $start, end $end, start_offset $start_offset";
362             #print ", end_offset $end_offset" if defined $end_offset ;
363             #print "\n";
364              
365 40         75 my @modified = () ;
366              
367             #If we are here, we need to test for whether we need to frag the
368             #conflicting element
369 40 100       136 if ($start_offset > 0) {
370 15         28 my $item = $self->[$start][2] ;
371 15         30 my $s = $self->[$start][0] ;
372 15         24 my $e = $start_elem-1 ;
373 15 100       51 my $new = defined($copy) ? $copy->($s,$e,$item) : $item ;
374 15         62 push @modified ,[$s, $e, $new ];
375             }
376              
377 40 100       140 push @modified, [$start_elem,$end_elem,$value] if defined $value ;
378              
379             #Do a fragmentation check
380 40 100 100     253 if (defined $end_offset
      100        
381             and $end_offset >= 0
382             and $end_elem < $self->[$end][1]
383             ) {
384 11         23 my $item = $self->[$end][2] ;
385 11         18 my $s = $end_elem+1 ;
386 11         21 my $e = $self->[$end][1] ;
387 11 100       32 my $new = defined($copy) ? $copy->($s,$e,$item) : $item ;
388 11         37 push @modified , [$s, $e, $new] ;
389             }
390              
391 40 100 100     187 my $extra = (defined $end_offset and $end_offset >= 0) ? 1 : 0 ;
392              
393 40         164 return ($start, $end - $start + $extra , @modified);
394             }
395              
396             sub lookup {
397 7     7 1 843 my $self = shift;
398 7         11 my($key) = @_;
399              
400 7         9 my($start, $end) = (0, $#{$self});
  7         14  
401 7 100       28 return undef unless $end >= 0 ; # completely empty span
402              
403 6         16 while ($start < $end) {
404 11         23 my $mid = int(($start+$end)/2);
405 11 100       25 if ($self->[$mid][1] < $key) {
406 7         16 $start = $mid+1;
407             } else {
408 4         11 $end = $mid;
409             }
410             }
411 6 50 33     33 if ($self->[$start]->[0] <= $key && $self->[$start]->[1] >= $key) {
412 6         35 return $self->[$start]->[2];
413             }
414 0         0 return undef;
415             }
416              
417             sub _check_structure {
418 56     56   75 my $self = shift;
419              
420 56 100       203 return unless $#$self >= 0;
421              
422 50         139 foreach my $i (0..$#$self) {
423 119 50       129 @{$self->[$i]} == 3 or
  119         304  
424             croak("Array::IntSpan::_check_structure failed - element $i lacks 3 entries.");
425 119 50       313 $self->[$i][0] <= $self->[$i][1] or
426             croak("Array::IntSpan::_check_structure failed - element $i has bad indices.");
427 119 100       275 if ($i > 0) {
428 69 50       242 $self->[$i-1][1] < $self->[$i][0] or
429             croak("Array::IntSpan::_check_structure failed - element $i (",
430             ,$self->[$i][0],",",$self->[$i][1],
431             ") doesn't come after previous element (",
432             $self->[$i-1][0],",",$self->[$i-1][1],")");
433             }
434             }
435             }
436              
437             #The following code is courtesy of Mark Jacob-Dominus,
438             sub croak {
439 0     0 0   require Carp;
440 8     8   72 no warnings 'redefine' ;
  8         19  
  8         639  
441 0           *croak = \&Carp::croak;
442 0           goto &croak;
443             }
444              
445             1;
446              
447             __END__
448              
449             =head1 NAME
450              
451             Array::IntSpan - Handles arrays of scalars or objects using IntSpan techniques
452              
453             =head1 SYNOPSIS
454              
455             use Array::IntSpan;
456              
457             my $foo = Array::IntSpan->new([0, 59, 'F'], [60, 69, 'D'], [80, 89, 'B']);
458              
459             print "A score of 84% results in a ".$foo->lookup(84).".\n";
460             unless (defined($foo->lookup(70))) {
461             print "The grade for the score 70% is currently undefined.\n";
462             }
463              
464             $foo->set_range(70, 79, 'C');
465             print "A score of 75% now results in a ".$foo->lookup(75).".\n";
466              
467             $foo->set_range(0, 59, undef);
468             unless (defined($foo->lookup(40))) {
469             print "The grade for the score 40% is now undefined.\n";
470             }
471              
472             $foo->set_range(87, 89, 'B+');
473             $foo->set_range(85, 100, 'A');
474             $foo->set_range(100, 1_000_000, 'A+');
475              
476             =head1 DESCRIPTION
477              
478             C<Array::IntSpan> brings the speed advantages of C<Set::IntSpan>
479             (written by Steven McDougall) to arrays. Uses include manipulating
480             grades, routing tables, or any other situation where you have mutually
481             exclusive ranges of integers that map to given values.
482              
483             The new version of C<Array::IntSpan> is also able to consolidate the
484             ranges by comparing the adjacent values of the range. If 2 adjacent
485             values are identical, the 2 adjacent ranges are merged.
486              
487             =head1 Ranges of objects
488              
489             C<Array::IntSpan> can also handle objects instead of scalar values.
490              
491             But for the consolidation to work, the payload class must overload the
492             C<"">, C<eq> and C<==> operators to perform the consolidation
493             comparisons.
494              
495             When a get_range method is called to a range of objects, it will
496             return a new range of object referencess. These object references
497             points to the objects stored in the original range. In other words the
498             objects contained in the returned range are B<not> copied.
499              
500             Thus if the user calls a methods on the objects contained in the
501             returned range, the method is actually invoked on the objects stored
502             in the original range.
503              
504             When a get_range method is called on a range of objects, several
505             things may happen:
506              
507             =over
508              
509             =item *
510              
511             The get_range spans empty slots. By default the returned range will
512             skip the empty slots. But the user may provide a callback to create
513             new objects (for instance). See details below.
514              
515             =item *
516              
517             The get_range splits existing ranges. By default, the split range will
518             contains the same object reference. The user may provide callback to
519             perform the object copy so that the split range will contains
520             different objects. See details below.
521              
522             =back
523              
524             =head1 Ranges specified with integer fields
525              
526             =over
527              
528             =item *
529              
530             C<Array::IntSpan::IP> is also provided with the distribution. It lets
531             you use IP addresses in any of three forms (dotted decimal, network
532             string, and integer) for the indices into the array. See the POD for
533             that module for more information. See L<Array::IntSpan::IP> for
534             details.
535              
536             =item *
537              
538             C<Array::IntSpan::Fields> is also provided with the distribution. It
539             let you specify an arbitrary specification to handle ranges with
540             strings made of several integer separared by dots (like IP addresses
541             of ANSI SS7 point codes). See L<Array::IntSpan::Fields> for details.
542              
543             =back
544              
545              
546             =head1 METHODS
547              
548             =head2 new (...)
549              
550             The C<new> method takes an optional list of array elements. The
551             elements should be in the form C<[start_index, end_index, value]>.
552             They should be in sorted order and there should be no overlaps. The
553             internal method C<_check_structure> will be called to verify the data
554             is correct. If you wish to avoid the performance penalties of
555             checking the structure, you can use C<Data::Dumper> to dump an object
556             and use that code to reconstitute it.
557              
558             =head2 clear
559              
560             Clear the range.
561              
562             =head2 set_range (start, end, value [, code ref] )
563              
564             This method takes three parameters - the C<start_index>, the
565             C<end_index>, and the C<value>. If you wish to erase a range, specify
566             C<undef> for the C<value>. It properly deals with overlapping ranges
567             and will replace existing data as appropriate. If the new range lies
568             after the last existing range, the method will execute in O(1) time.
569             If the new range lies within the existing ranges, the method executes
570             in O(n) time, where n is the number of ranges. It does not consolidate
571             contiguous ranges that have the same C<value>.
572              
573             If you have a large number of inserts to do, it would be beneficial to
574             sort them first. Sorting is O(n lg(n)), and since appending is O(1),
575             that will be considerably faster than the O(n^2) time for inserting n
576             unsorted elements.
577              
578             The method returns C<0> if there were no overlapping ranges and C<1>
579             if there were.
580              
581             The optional code ref is called back when an existing range is
582             split. For instance if the original range is C<[0,10,$foo_obj]> and
583             set_range is called with C<[5,7,$bar_obj']>, the callback will be called
584             twice:
585              
586             $callback->(0, 4,$foo_obj)
587             $callback->(8,10,$foo_obj)
588              
589             It will be the callback responsability to make sure that the range
590             C<0-4> and C<7-10> holds 2 I<different> objects.
591              
592             =head2 get_range (start, end [, filler | undef , copy_cb [, set_cb]])
593              
594             This method returns a range (actually an Array::IntSpan object) from
595             C<start> to C<end>.
596              
597             If C<start> and C<end> span empty slot in the original range,
598             get_range will skip the empty slots. If a C<filler> value is provided,
599             get_range will fill the slots with it.
600              
601             original range : [2-4,X],[7-9,Y],[12-14,Z]
602             get_range(3,8) : [3-4,X],[7-8,Y]
603             get_range(2,10,f) : [3-4,X],[5-6,f],[7-8,Y]
604              
605             If the C<filler> parameter is a CODE reference, the filler value will
606             be the one returned by the sub ref. The sub ref is invoked with
607             C<(start,end)>, i.e. the range of the empty span to fill
608             (C<get_range(5,6)> in the example above). When handling object, the
609             sub ref can invoke an object constructor.
610              
611             If C<start> or C<end> split an original range in 2, the default
612             behavior is to copy the value or object ref contained in the original
613             range:
614              
615             original range : [1-4,X]
616             split range : [1-1,X],[2-2,X],[3-4,X]
617             get_range(2) : [2-2,X]
618              
619             If the original range contains object, this may lead to
620             disapointing results. In the example below the 2 ranges contains
621             references (C<obj_a>) that points to the same object:
622              
623             original range : [1-4,obj_a]
624             split range : [1-1,obj_a],[2-2,obj_a],[3-4,obj_a]
625             get_range(2) : [2-2,obj_a]
626              
627             Which means that invoking a method on the object returned by
628             C<get_range(2)> will also be invoked on the range 1-4 of the original
629             range which may not be what you want.
630              
631             If C<get_range> is invoked with a copy parameter (actually a code
632             reference), the result of this routine will be stored in the split
633             range I<outside> of the get_range:
634              
635             original range : [1-4,X]
636             get_range(2) : [2-2,X]
637             split range : [1-1,copy_of_X],[2-2,X],[3-4,copy_of_X]
638              
639             When dealing with object, the sub ref should provide a copy of the object:
640              
641             original range : [1-4,obj_a]
642             get_range(2) : [2-2,obj_a]
643             split range : [1-1,obj_a1],[2-2,obj_a],[3-4,obj_a2]
644              
645             Note that the C<obj_a> contained in the C<split range> and the
646             C<obj_a> contained in the returned range point to the I<same object>.
647              
648             The sub ref is invoked with C<(start,end,obj_a)> and is expected to
649             return a copy of C<obj_a> that will be stored in the split ranges. In
650             the example above, 2 different copies are made: C<obj_a1> and
651             C<obj_a2>.
652              
653             Last, a 3rd callback may be defined by the user: the C<set_cb>. This
654             callback will be used when the range start or end that holds an object
655             changes. In the example above, the C<set_cb> will be called this way:
656              
657             $obj_a->&$set_cb(2,2) ;
658              
659             As a matter of fact, the 3 callback can be used in the same call. In
660             the example below, C<get_range> is invoked with 3 subs refs:
661             C<\&f,\&cp,\&set>:
662              
663             original range : [1-4,obj_a],[7-9,obj_b]
664             get_range(3-8,...) : [3-4,obj_a],[5-6,obj_fill],[7-8,obj_b]
665             split range : [1-2,obj_a1], [3-4,obj_a],[5-6,obj_fill],
666             [7-8,obj_b],[9-9,obj_b1]
667              
668             To obtain this, get_range will perform the following calls:
669              
670             $obj_fill = &f ;
671             $obj_a1 = &cp(5,6,obj_a);
672             &set(3,4,$obj_a) ;
673             $obj_b = &cp(9,9,obj_b) ;
674             &set(7-8,obj_b) ;
675              
676             =head2 lookup( index )
677              
678             This method takes as a single parameter the C<index> to look up. If
679             there is an appropriate range, the method will return the associated
680             value. Otherwise, it returns C<undef>.
681              
682             =head2 get_element( element_number )
683              
684             Returns an array containing the Nth range element:
685              
686             ( start, end, value )
687              
688             =head2 consolidate( bottom, top , [ set_cb ] )
689              
690             This function scan the range from the range index C<bottom> to C<top>
691             and compare the values held by the adjacent ranges. If the values are
692             identical, the adjacent ranges are merged.
693              
694             The comparision is made with the C<==> operator. Objects stored in the
695             range B<must> overload the C<==> operator. If not, the comparison will
696             be made with the standard stringification of an object and the merge
697             will never happen.
698              
699             If provided, the C<set_cb> will be invoked on the contained object
700             after 2 ranges are merged.
701              
702             For instance, if the C<"$obj_a" eq "$obj_b">:
703              
704             original range : [1-4,obj_a],[5-9,obj_b]
705             consolidate(0,1,\&set) : [1-9,obj_a]
706              
707             And consolidate will perform this call:
708              
709             &$set(1,9,obj_a) ;
710              
711              
712             =head1 AUTHOR
713              
714             =over
715              
716             =item *
717              
718             Toby Everett, teverett@alascom.att.com
719              
720             =item *
721              
722             Dominique Dumont, dominique.dumont@hp.com
723              
724             =back
725              
726             Copyright (c) 2000 Toby Everett.
727             Copyright (c) 2003-2004 Dominique Dumont.
728             All rights reserved. This program is free software.
729              
730             This module is distributed under the Artistic License. See
731             http://www.ActiveState.com/corporate/artistic_license.htm or the
732             license that comes with your perl distribution.
733              
734             =cut
735