File Coverage

lib/Algorithm/Diff.pm
Criterion Covered Total %
statement 312 356 87.6
branch 107 136 78.6
condition 74 95 77.8
subroutine 42 52 80.7
pod 9 9 100.0
total 544 648 83.9


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