File Coverage

lib/Algorithm/Diff.pm
Criterion Covered Total %
statement 312 350 89.1
branch 107 136 78.6
condition 74 95 77.8
subroutine 42 52 80.7
pod 9 9 100.0
total 544 642 84.7


line stmt bran cond sub pod time code
1             package Algorithm::Diff;
2             # Skip to first "=head" line for documentation.
3 2     2   3986 use strict;
  2         9  
  2         60  
4              
5 2     2   1123 use integer; # see below in _replaceNextLargerWith() for mod to make
  2         28  
  2         11  
6             # if you don't use this
7 2     2   87 use vars qw( $VERSION @EXPORT_OK );
  2         4  
  2         5178  
8             $VERSION = '1.200';
9              
10             require Exporter;
11             *import = \&Exporter::import;
12             @EXPORT_OK = qw(
13             prepare LCS LCSidx LCS_length
14             diff sdiff compact_diff
15             traverse_sequences traverse_balanced
16             );
17              
18             # McIlroy-Hunt diff algorithm
19             # Adapted from the Smalltalk code of Mario I. Wolczko,
20             # by Ned Konz, perl@bike-nomad.com
21             # Updates by Tye McQueen, http://perlmonks.org/?node=tye
22              
23             # Create a hash that maps each element of $aCollection to the set of
24             # positions it occupies in $aCollection, restricted to the elements
25             # within the range of indexes specified by $start and $end.
26             # The fourth parameter is a subroutine reference that will be called to
27             # generate a string to use as a key.
28             # Additional parameters, if any, will be passed to this subroutine.
29             #
30             # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
31              
32             sub _withPositionsOfInInterval
33             {
34 36     36   45 my $aCollection = shift; # array ref
35 36         38 my $start = shift;
36 36         45 my $end = shift;
37 36         37 my $keyGen = shift;
38 36         50 my %d;
39             my $index;
40 36         69 for ( $index = $start ; $index <= $end ; $index++ )
41             {
42 154         176 my $element = $aCollection->[$index];
43 154         199 my $key = &$keyGen( $element, @_ );
44 154 100       207 if ( exists( $d{$key} ) )
45             {
46 1         1 unshift ( @{ $d{$key} }, $index );
  1         3  
47             }
48             else
49             {
50 153         377 $d{$key} = [$index];
51             }
52             }
53 36 50       114 return wantarray ? %d : \%d;
54             }
55              
56             # Find the place at which aValue would normally be inserted into the
57             # array. If that place is already occupied by aValue, do nothing, and
58             # return undef. If the place does not exist (i.e., it is off the end of
59             # the array), add it to the end, otherwise replace the element at that
60             # point with aValue. It is assumed that the array's values are numeric.
61             # This is where the bulk (75%) of the time is spent in this module, so
62             # try to make it fast!
63              
64             sub _replaceNextLargerWith
65             {
66 55     55   86 my ( $array, $aValue, $high ) = @_;
67 55   66     173 $high ||= $#$array;
68              
69             # off the end?
70 55 50 66     148 if ( $high == -1 || $aValue > $array->[-1] )
71             {
72 55         79 push ( @$array, $aValue );
73 55         87 return $high + 1;
74             }
75              
76             # binary search for insertion point...
77 0         0 my $low = 0;
78 0         0 my $index;
79             my $found;
80 0         0 while ( $low <= $high )
81             {
82 0         0 $index = ( $high + $low ) / 2;
83              
84             # $index = int(( $high + $low ) / 2); # without 'use integer'
85 0         0 $found = $array->[$index];
86              
87 0 0       0 if ( $aValue == $found )
    0          
88             {
89 0         0 return undef;
90             }
91             elsif ( $aValue > $found )
92             {
93 0         0 $low = $index + 1;
94             }
95             else
96             {
97 0         0 $high = $index - 1;
98             }
99             }
100              
101             # now insertion point is in $low.
102 0         0 $array->[$low] = $aValue; # overwrite next larger
103 0         0 return $low;
104             }
105              
106             # This method computes the longest common subsequence in $a and $b.
107              
108             # Result is array or ref, whose contents is such that
109             # $a->[ $i ] == $b->[ $result[ $i ] ]
110             # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
111              
112             # An additional argument may be passed; this is a hash or key generating
113             # function that should return a string that uniquely identifies the given
114             # element. It should be the case that if the key is the same, the elements
115             # will compare the same. If this parameter is undef or missing, the key
116             # will be the element as a string.
117              
118             # By default, comparisons will use "eq" and elements will be turned into keys
119             # using the default stringizing operator '""'.
120              
121             # Additional parameters, if any, will be passed to the key generation
122             # routine.
123              
124             sub _longestCommonSubsequence
125             {
126 36     36   89 my $a = shift; # array ref or hash ref
127 36         38 my $b = shift; # array ref or hash ref
128 36         39 my $counting = shift; # scalar
129 36         42 my $keyGen = shift; # code ref
130 36         35 my $compare; # code ref
131              
132 36 50       89 if ( ref($a) eq 'HASH' )
133             { # prepared hash must be in $b
134 0         0 my $tmp = $b;
135 0         0 $b = $a;
136 0         0 $a = $tmp;
137             }
138              
139             # Check for bogus (non-ref) argument values
140 36 50 33     132 if ( !ref($a) || !ref($b) )
141             {
142 0         0 my @callerInfo = caller(1);
143 0         0 die 'error: must pass array or hash references to ' . $callerInfo[3];
144             }
145              
146             # set up code refs
147             # Note that these are optimized.
148 36 50       70 if ( !defined($keyGen) ) # optimize for strings
149             {
150 36     283   118 $keyGen = sub { $_[0] };
  283         364  
151 36     81   94 $compare = sub { my ( $a, $b ) = @_; $a eq $b };
  81         128  
  81         233  
152             }
153             else
154             {
155             $compare = sub {
156 0     0   0 my $a = shift;
157 0         0 my $b = shift;
158 0         0 &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
159 0         0 };
160             }
161              
162 36         80 my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
163 36         68 my ( $prunedCount, $bMatches ) = ( 0, {} );
164              
165 36 50       68 if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us?
166             {
167 0         0 $bMatches = $b;
168             }
169             else
170             {
171 36         50 my ( $bStart, $bFinish ) = ( 0, $#$b );
172              
173             # First we prune off any common elements at the beginning
174 36   100     158 while ( $aStart <= $aFinish
      100        
175             and $bStart <= $bFinish
176             and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
177             {
178 13         40 $matchVector->[ $aStart++ ] = $bStart++;
179 13         40 $prunedCount++;
180             }
181              
182             # now the end
183 36   100     139 while ( $aStart <= $aFinish
      100        
184             and $bStart <= $bFinish
185             and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
186             {
187 16         27 $matchVector->[ $aFinish-- ] = $bFinish--;
188 16         44 $prunedCount++;
189             }
190              
191             # Now compute the equivalence classes of positions of elements
192             $bMatches =
193 36         73 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
194             }
195 36         48 my $thresh = [];
196 36         51 my $links = [];
197              
198 36         47 my ( $i, $ai, $j, $k );
199 36         70 for ( $i = $aStart ; $i <= $aFinish ; $i++ )
200             {
201 129         192 $ai = &$keyGen( $a->[$i], @_ );
202 129 100       241 if ( exists( $bMatches->{$ai} ) )
203             {
204 55         57 $k = 0;
205 55         54 for $j ( @{ $bMatches->{$ai} } )
  55         97  
206             {
207              
208             # optimization: most of the time this will be true
209 55 50 33     120 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
      33        
210             {
211 0         0 $thresh->[$k] = $j;
212             }
213             else
214             {
215 55         79 $k = _replaceNextLargerWith( $thresh, $j, $k );
216             }
217              
218             # oddly, it's faster to always test this (CPU cache?).
219 55 50       77 if ( defined($k) )
220             {
221 55 100       199 $links->[$k] =
222             [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
223             }
224             }
225             }
226             }
227              
228 36 100       79 if (@$thresh)
    50          
229             {
230 14 50       33 return $prunedCount + @$thresh if $counting;
231 14         43 for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
232             {
233 55         100 $matchVector->[ $link->[1] ] = $link->[2];
234             }
235             }
236             elsif ($counting)
237             {
238 0         0 return $prunedCount;
239             }
240              
241 36 100       219 return wantarray ? @$matchVector : $matchVector;
242             }
243              
244             sub traverse_sequences
245             {
246 3     3 1 528 my $a = shift; # array ref
247 3         3 my $b = shift; # array ref
248 3   50     8 my $callbacks = shift || {};
249 3         4 my $keyGen = shift;
250 3   50 0   8 my $matchCallback = $callbacks->{'MATCH'} || sub { };
251 3   50 0   6 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
252 3         4 my $finishedACallback = $callbacks->{'A_FINISHED'};
253 3   50 0   16 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
254 3         6 my $finishedBCallback = $callbacks->{'B_FINISHED'};
255 3         6 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
256              
257             # Process all the lines in @$matchVector
258 3         5 my $lastA = $#$a;
259 3         5 my $lastB = $#$b;
260 3         3 my $bi = 0;
261 3         4 my $ai;
262              
263 3         7 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
264             {
265 24         98 my $bLine = $matchVector->[$ai];
266 24 100       33 if ( defined($bLine) ) # matched
267             {
268 18         32 &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
269 18         46 &$matchCallback( $ai, $bi++, @_ );
270             }
271             else
272             {
273 6         10 &$discardACallback( $ai, $bi, @_ );
274             }
275             }
276              
277             # The last entry (if any) processed was a match.
278             # $ai and $bi point just past the last matching lines in their sequences.
279              
280 3   100     32 while ( $ai <= $lastA or $bi <= $lastB )
281             {
282              
283             # last A?
284 9 100 66     43 if ( $ai == $lastA + 1 and $bi <= $lastB )
285             {
286 3 100       5 if ( defined($finishedACallback) )
287             {
288 1         3 &$finishedACallback( $lastA, @_ );
289 1         5 $finishedACallback = undef;
290             }
291             else
292             {
293 2         7 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
294             }
295             }
296              
297             # last B?
298 9 50 66     23 if ( $bi == $lastB + 1 and $ai <= $lastA )
299             {
300 0 0       0 if ( defined($finishedBCallback) )
301             {
302 0         0 &$finishedBCallback( $lastB, @_ );
303 0         0 $finishedBCallback = undef;
304             }
305             else
306             {
307 0         0 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
308             }
309             }
310              
311 9 100       21 &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
312 9 100       40 &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
313             }
314              
315 3         11 return 1;
316             }
317              
318             sub traverse_balanced
319             {
320 20     20 1 966 my $a = shift; # array ref
321 20         22 my $b = shift; # array ref
322 20   50     40 my $callbacks = shift || {};
323 20         21 my $keyGen = shift;
324 20   50 0   37 my $matchCallback = $callbacks->{'MATCH'} || sub { };
325 20   50 0   39 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
326 20   50 0   34 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
327 20         24 my $changeCallback = $callbacks->{'CHANGE'};
328 20         38 my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
329              
330             # Process all the lines in match vector
331 20         27 my $lastA = $#$a;
332 20         27 my $lastB = $#$b;
333 20         22 my $bi = 0;
334 20         20 my $ai = 0;
335 20         21 my $ma = -1;
336 20         21 my $mb;
337              
338 20         20 while (1)
339             {
340              
341             # Find next match indices $ma and $mb
342 52   100     86 do {
343 71         191 $ma++;
344             } while(
345             $ma <= $#$matchVector
346             && !defined $matchVector->[$ma]
347             );
348              
349 52 100       157 last if $ma > $#$matchVector; # end of matchVector?
350 32         37 $mb = $matchVector->[$ma];
351              
352             # Proceed with discard a/b or change events until
353             # next match
354 32   100     77 while ( $ai < $ma || $bi < $mb )
355             {
356              
357 38 100 100     99 if ( $ai < $ma && $bi < $mb )
    100          
358             {
359              
360             # Change
361 10 100       66 if ( defined $changeCallback )
362             {
363 9         18 &$changeCallback( $ai++, $bi++, @_ );
364             }
365             else
366             {
367 1         4 &$discardACallback( $ai++, $bi, @_ );
368 1         6 &$discardBCallback( $ai, $bi++, @_ );
369             }
370             }
371             elsif ( $ai < $ma )
372             {
373 9         352 &$discardACallback( $ai++, $bi, @_ );
374             }
375             else
376             {
377              
378             # $bi < $mb
379 19         29 &$discardBCallback( $ai, $bi++, @_ );
380             }
381             }
382              
383             # Match
384 32         81 &$matchCallback( $ai++, $bi++, @_ );
385             }
386              
387 20   100     51 while ( $ai <= $lastA || $bi <= $lastB )
388             {
389 22 100 100     76 if ( $ai <= $lastA && $bi <= $lastB )
    100          
390             {
391              
392             # Change
393 10 50       18 if ( defined $changeCallback )
394             {
395 10         16 &$changeCallback( $ai++, $bi++, @_ );
396             }
397             else
398             {
399 0         0 &$discardACallback( $ai++, $bi, @_ );
400 0         0 &$discardBCallback( $ai, $bi++, @_ );
401             }
402             }
403             elsif ( $ai <= $lastA )
404             {
405 7         12 &$discardACallback( $ai++, $bi, @_ );
406             }
407             else
408             {
409              
410             # $bi <= $lastB
411 5         7 &$discardBCallback( $ai, $bi++, @_ );
412             }
413             }
414              
415 20         52 return 1;
416             }
417              
418             sub prepare
419             {
420 0     0 1 0 my $a = shift; # array ref
421 0         0 my $keyGen = shift; # code ref
422              
423             # set up code ref
424 0 0   0   0 $keyGen = sub { $_[0] } unless defined($keyGen);
  0         0  
425              
426 0         0 return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
427             }
428              
429             sub LCS
430             {
431 1     1 1 317 my $a = shift; # array ref
432 1         2 my $b = shift; # array ref or hash ref
433 1         4 my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
434 1         2 my @retval;
435             my $i;
436 1         6 for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
437             {
438 8 100       11 if ( defined( $matchVector->[$i] ) )
439             {
440 6         13 push ( @retval, $a->[$i] );
441             }
442             }
443 1 50       61 return wantarray ? @retval : \@retval;
444             }
445              
446             sub LCS_length
447             {
448 0     0 1 0 my $a = shift; # array ref
449 0         0 my $b = shift; # array ref or hash ref
450 0         0 return _longestCommonSubsequence( $a, $b, 1, @_ );
451             }
452              
453             sub LCSidx
454             {
455 11     11 1 14 my $a= shift @_;
456 11         15 my $b= shift @_;
457 11         27 my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
458 11         51 my @am= grep defined $match->[$_], 0..$#$match;
459 11         33 my @bm= @{$match}[@am];
  11         25  
460 11         34 return \@am, \@bm;
461             }
462              
463             sub compact_diff
464             {
465 11     11 1 14 my $a= shift @_;
466 11         14 my $b= shift @_;
467 11         25 my( $am, $bm )= LCSidx( $a, $b, @_ );
468 11         13 my @cdiff;
469 11         13 my( $ai, $bi )= ( 0, 0 );
470 11         22 push @cdiff, $ai, $bi;
471 11         12 while( 1 ) {
472 22   100     82 while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) {
      100        
473 22         26 shift @$am;
474 22         27 shift @$bm;
475 22         60 ++$ai, ++$bi;
476             }
477 22         26 push @cdiff, $ai, $bi;
478 22 100       37 last if ! @$am;
479 11         14 $ai = $am->[0];
480 11         11 $bi = $bm->[0];
481 11         17 push @cdiff, $ai, $bi;
482             }
483 11 100 100     41 push @cdiff, 0+@$a, 0+@$b
484             if $ai < @$a || $bi < @$b;
485 11 50       25 return wantarray ? @cdiff : \@cdiff;
486             }
487              
488             sub diff
489             {
490 1     1 1 89 my $a = shift; # array ref
491 1         2 my $b = shift; # array ref
492 1         2 my $retval = [];
493 1         2 my $hunk = [];
494             my $discard = sub {
495 4     4   8 push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
496 1         5 };
497             my $add = sub {
498 6     6   14 push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
499 1         4 };
500             my $match = sub {
501 7 100   7   13 push @$retval, $hunk
502             if 0 < @$hunk;
503 7         12 $hunk = []
504 1         3 };
505 1         6 traverse_sequences( $a, $b,
506             { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
507 1         3 &$match();
508 1 50       7 return wantarray ? @$retval : $retval;
509             }
510              
511             sub sdiff
512             {
513 12     12 1 3135 my $a = shift; # array ref
514 12         16 my $b = shift; # array ref
515 12         16 my $retval = [];
516 12     13   43 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
  13         43  
517 12     24   26 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
  24         77  
518             my $change = sub {
519 8     8   30 push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
520 12         26 };
521             my $match = sub {
522 23     23   48 push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
523 12         26 };
524 12         57 traverse_balanced(
525             $a,
526             $b,
527             {
528             MATCH => $match,
529             DISCARD_A => $discard,
530             DISCARD_B => $add,
531             CHANGE => $change,
532             },
533             @_
534             );
535 12 50       101 return wantarray ? @$retval : $retval;
536             }
537              
538             ########################################
539             my $Root= __PACKAGE__;
540             package Algorithm::Diff::_impl;
541 2     2   16 use strict;
  2         12  
  2         3140  
542              
543             sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices
544             # 1 # $me->[1]: Ref to first sequence
545             # 2 # $me->[2]: Ref to second sequence
546             sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos
547             sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
548             sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
549             sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected
550             sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position
551             sub _Min() { -2 } # Added to _Off to get min instead of max+1
552              
553             sub Die
554             {
555 230     230   1032 require Carp;
556 230         26425 Carp::confess( @_ );
557             }
558              
559             sub _ChkPos
560             {
561 1052     1052   1254 my( $me )= @_;
562 1052 100       1849 return if $me->[_Pos];
563 110         616 my $meth= ( caller(1) )[3];
564 110         326 Die( "Called $meth on 'reset' object" );
565             }
566              
567             sub _ChkSeq
568             {
569 650     650   816 my( $me, $seq )= @_;
570 650 100 100     1804 return $seq + $me->[_Off]
571             if 1 == $seq || 2 == $seq;
572 110         581 my $meth= ( caller(1) )[3];
573 110         366 Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
574             }
575              
576             sub getObjPkg
577             {
578 13     13   23 my( $us )= @_;
579 13 50       32 return ref $us if ref $us;
580 13         39 return $us . "::_obj";
581             }
582              
583             sub new
584             {
585 11     11   1965 my( $us, $seq1, $seq2, $opts ) = @_;
586 11         13 my @args;
587 11         35 for( $opts->{keyGen} ) {
588 11 50       28 push @args, $_ if $_;
589             }
590 11         36 for( $opts->{keyGenArgs} ) {
591 11 50       23 push @args, @$_ if $_;
592             }
593 11         33 my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
594 11         39 my $same= 1;
595 11 100 66     42 if( 0 == $cdif->[2] && 0 == $cdif->[3] ) {
596 8         10 $same= 0;
597 8         17 splice @$cdif, 0, 2;
598             }
599 11         20 my @obj= ( $cdif, $seq1, $seq2 );
600 11         20 $obj[_End] = (1+@$cdif)/2;
601 11         24 $obj[_Same] = $same;
602 11         21 $obj[_Base] = 0;
603 11         34 my $me = bless \@obj, $us->getObjPkg();
604 11         28 $me->Reset( 0 );
605 11         37 return $me;
606             }
607              
608             sub Reset
609             {
610 330     330   2256 my( $me, $pos )= @_;
611 330   100     620 $pos= int( $pos || 0 );
612 330 100       505 $pos += $me->[_End]
613             if $pos < 0;
614 330 100 100     874 $pos= 0
615             if $pos < 0 || $me->[_End] <= $pos;
616 330   100     588 $me->[_Pos]= $pos || !1;
617 330         414 $me->[_Off]= 2*$pos - 1;
618 330         510 return $me;
619             }
620              
621             sub Base
622             {
623 162     162   320 my( $me, $base )= @_;
624 162         188 my $oldBase= $me->[_Base];
625 162 100       276 $me->[_Base]= 0+$base if defined $base;
626 162         372 return $oldBase;
627             }
628              
629             sub Copy
630             {
631 54     54   126 my( $me, $pos, $base )= @_;
632 54         141 my @obj= @$me;
633 54         93 my $you= bless \@obj, ref($me);
634 54 100       103 $you->Reset( $pos ) if defined $pos;
635 54         102 $you->Base( $base );
636 54         161 return $you;
637             }
638              
639             sub Next {
640 347     347   5154 my( $me, $steps )= @_;
641 347 100       586 $steps= 1 if ! defined $steps;
642 347 100       493 if( $steps ) {
643 242         332 my $pos= $me->[_Pos];
644 242         261 my $new= $pos + $steps;
645 242 100 100     573 $new= 0 if $pos && $new < 0;
646 242         723 $me->Reset( $new )
647             }
648 347         777 return $me->[_Pos];
649             }
650              
651             sub Prev {
652 109     109   4718 my( $me, $steps )= @_;
653 109 100       196 $steps= 1 if ! defined $steps;
654 109         233 my $pos= $me->Next(-$steps);
655 109 100       195 $pos -= $me->[_End] if $pos;
656 109         298 return $pos;
657             }
658              
659             sub Diff {
660 81     81   129 my( $me )= @_;
661 81         165 $me->_ChkPos();
662 70 100       154 return 0 if $me->[_Same] == ( 1 & $me->[_Pos] );
663 50         54 my $ret= 0;
664 50         50 my $off= $me->[_Off];
665 50         82 for my $seq ( 1, 2 ) {
666 100 100       214 $ret |= $seq
667             if $me->[_Idx][ $off + $seq + _Min ]
668             < $me->[_Idx][ $off + $seq ];
669             }
670 50         115 return $ret;
671             }
672              
673             sub Min {
674 91     91   6030 my( $me, $seq, $base )= @_;
675 91         180 $me->_ChkPos();
676 80         131 my $off= $me->_ChkSeq($seq);
677 50 100       97 $base= $me->[_Base] if !defined $base;
678 50         131 return $base + $me->[_Idx][ $off + _Min ];
679             }
680              
681             sub Max {
682 91     91   5937 my( $me, $seq, $base )= @_;
683 91         190 $me->_ChkPos();
684 80         149 my $off= $me->_ChkSeq($seq);
685 50 100       86 $base= $me->[_Base] if !defined $base;
686 50         135 return $base + $me->[_Idx][ $off ] -1;
687             }
688              
689             sub Range {
690 295     295   6356 my( $me, $seq, $base )= @_;
691 295         563 $me->_ChkPos();
692 284         414 my $off = $me->_ChkSeq($seq);
693 254 100       425 if( !wantarray ) {
694 104         359 return $me->[_Idx][ $off ]
695             - $me->[_Idx][ $off + _Min ];
696             }
697 150 100       222 $base= $me->[_Base] if !defined $base;
698 150         642 return ( $base + $me->[_Idx][ $off + _Min ] )
699             .. ( $base + $me->[_Idx][ $off ] - 1 );
700             }
701              
702             sub Items {
703 217     217   4681 my( $me, $seq )= @_;
704 217         408 $me->_ChkPos();
705 206         308 my $off = $me->_ChkSeq($seq);
706 186 100       347 if( !wantarray ) {
707 48         119 return $me->[_Idx][ $off ]
708             - $me->[_Idx][ $off + _Min ];
709             }
710             return
711 138         229 @{$me->[$seq]}[
  138         631  
712             $me->[_Idx][ $off + _Min ]
713             .. ( $me->[_Idx][ $off ] - 1 )
714             ];
715             }
716              
717             sub Same {
718 156     156   4193 my( $me )= @_;
719 156         308 $me->_ChkPos();
720 112 50       302 return wantarray ? () : 0
    100          
721             if $me->[_Same] != ( 1 & $me->[_Pos] );
722 62         117 return $me->Items(1);
723             }
724              
725             my %getName;
726             BEGIN {
727 2     2   679 %getName= (
728             same => \&Same,
729             diff => \&Diff,
730             base => \&Base,
731             min => \&Min,
732             max => \&Max,
733             range=> \&Range,
734             items=> \&Items, # same thing
735             );
736             }
737              
738             sub Get
739             {
740 121     121   6711 my $me= shift @_;
741 121         236 $me->_ChkPos();
742 110         131 my @value;
743 110         165 for my $arg ( @_ ) {
744 130         285 for my $word ( split ' ', $arg ) {
745 160         157 my $meth;
746 160 50 33     1208 if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
747             || not $meth= $getName{ lc $2 }
748             ) {
749 0         0 Die( $Root, ", Get: Invalid request ($word)" );
750             }
751 160         384 my( $base, $name, $seq )= ( $1, $2, $3 );
752 160 100       360 push @value, scalar(
753             4 == length($name)
754             ? $meth->( $me )
755             : $meth->( $me, $seq, $base )
756             );
757             }
758             }
759 80 100       149 if( wantarray ) {
    100          
760 30         185 return @value;
761             } elsif( 1 == @value ) {
762 40         142 return $value[0];
763             }
764 10         22 Die( 0+@value, " values requested from ",
765             $Root, "'s Get in scalar context" );
766             }
767              
768              
769             my $Obj= getObjPkg($Root);
770 2     2   15 no strict 'refs';
  2         4  
  2         267  
771              
772             for my $meth ( qw( new getObjPkg ) ) {
773             *{$Root."::".$meth} = \&{$meth};
774             *{$Obj ."::".$meth} = \&{$meth};
775             }
776             for my $meth ( qw(
777             Next Prev Reset Copy Base Diff
778             Same Items Range Min Max Get
779             _ChkPos _ChkSeq
780             ) ) {
781             *{$Obj."::".$meth} = \&{$meth};
782             }
783              
784             1;
785             __END__