Algorithm-Diff
view release on metacpan or search on metacpan
lib/Algorithm/Diff.pm view on Meta::CPAN
sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices
# 1 # $me->[1]: Ref to first sequence
# 2 # $me->[2]: Ref to second sequence
sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos
sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected
sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position
sub _Min() { -2 } # Added to _Off to get min instead of max+1
sub Die
{
require Carp;
Carp::confess( @_ );
}
sub _ChkPos
{
my( $me )= @_;
return if $me->[_Pos];
my $meth= ( caller(1) )[3];
Die( "Called $meth on 'reset' object" );
}
sub _ChkSeq
{
my( $me, $seq )= @_;
return $seq + $me->[_Off]
if 1 == $seq || 2 == $seq;
my $meth= ( caller(1) )[3];
Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
}
sub getObjPkg
{
my( $us )= @_;
return ref $us if ref $us;
return $us . "::_obj";
}
sub new
{
my( $us, $seq1, $seq2, $opts ) = @_;
my @args;
for( $opts->{keyGen} ) {
push @args, $_ if $_;
}
for( $opts->{keyGenArgs} ) {
push @args, @$_ if $_;
}
my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
my $same= 1;
if( 0 == $cdif->[2] && 0 == $cdif->[3] ) {
$same= 0;
splice @$cdif, 0, 2;
}
my @obj= ( $cdif, $seq1, $seq2 );
$obj[_End] = (1+@$cdif)/2;
$obj[_Same] = $same;
$obj[_Base] = 0;
my $me = bless \@obj, $us->getObjPkg();
$me->Reset( 0 );
return $me;
}
sub Reset
{
my( $me, $pos )= @_;
$pos= int( $pos || 0 );
$pos += $me->[_End]
if $pos < 0;
$pos= 0
if $pos < 0 || $me->[_End] <= $pos;
$me->[_Pos]= $pos || !1;
$me->[_Off]= 2*$pos - 1;
return $me;
}
sub Base
{
my( $me, $base )= @_;
my $oldBase= $me->[_Base];
$me->[_Base]= 0+$base if defined $base;
return $oldBase;
}
sub Copy
{
my( $me, $pos, $base )= @_;
my @obj= @$me;
my $you= bless \@obj, ref($me);
$you->Reset( $pos ) if defined $pos;
$you->Base( $base );
return $you;
}
sub Next {
my( $me, $steps )= @_;
$steps= 1 if ! defined $steps;
if( $steps ) {
my $pos= $me->[_Pos];
my $new= $pos + $steps;
$new= 0 if $pos && $new < 0;
$me->Reset( $new )
}
return $me->[_Pos];
}
sub Prev {
my( $me, $steps )= @_;
$steps= 1 if ! defined $steps;
my $pos= $me->Next(-$steps);
$pos -= $me->[_End] if $pos;
return $pos;
}
sub Diff {
my( $me )= @_;
$me->_ChkPos();
return 0 if $me->[_Same] == ( 1 & $me->[_Pos] );
my $ret= 0;
my $off= $me->[_Off];
for my $seq ( 1, 2 ) {
$ret |= $seq
if $me->[_Idx][ $off + $seq + _Min ]
< $me->[_Idx][ $off + $seq ];
}
return $ret;
}
sub Min {
my( $me, $seq, $base )= @_;
$me->_ChkPos();
my $off= $me->_ChkSeq($seq);
$base= $me->[_Base] if !defined $base;
return $base + $me->[_Idx][ $off + _Min ];
}
sub Max {
my( $me, $seq, $base )= @_;
$me->_ChkPos();
my $off= $me->_ChkSeq($seq);
$base= $me->[_Base] if !defined $base;
return $base + $me->[_Idx][ $off ] -1;
}
sub Range {
my( $me, $seq, $base )= @_;
$me->_ChkPos();
my $off = $me->_ChkSeq($seq);
if( !wantarray ) {
lib/Algorithm/Diff.pm view on Meta::CPAN
C<traverse_balanced> is an alternative to C<traverse_sequences>. It
uses a different algorithm to iterate through the entries in the
computed LCS. Instead of sticking to one side and showing element changes
as insertions and deletions only, it will jump back and forth between
the two sequences and report I<changes> occurring as deletions on one
side followed immediately by an insertion on the other side.
In addition to the C<DISCARD_A>, C<DISCARD_B>, and C<MATCH> callbacks
supported by C<traverse_sequences>, C<traverse_balanced> supports
a C<CHANGE> callback indicating that one element got C<replaced> by another:
traverse_balanced(
\@seq1, \@seq2,
{ MATCH => $callback_1,
DISCARD_A => $callback_2,
DISCARD_B => $callback_3,
CHANGE => $callback_4,
}
);
If no C<CHANGE> callback is specified, C<traverse_balanced>
will map C<CHANGE> events to C<DISCARD_A> and C<DISCARD_B> actions,
therefore resulting in a similar behaviour as C<traverse_sequences>
with different order of events.
C<traverse_balanced> might be a bit slower than C<traverse_sequences>,
noticeable only while processing huge amounts of data.
The C<sdiff> function of this module
is implemented as call to C<traverse_balanced>.
C<traverse_balanced> does not have a useful return value; you are expected to
plug in the appropriate behavior with the callback functions.
=head1 KEY GENERATION FUNCTIONS
Most of the functions accept an optional extra parameter. This is a
CODE reference to a key generating (hashing) function that should return
a string that uniquely identifies a given element. It should be the
case that if two elements are to be considered equal, their keys should
be the same (and the other way around). If no key generation function
is provided, the key will be the element as a string.
By default, comparisons will use "eq" and elements will be turned into keys
using the default stringizing operator '""'.
Where this is important is when you're comparing something other than
strings. If it is the case that you have multiple different objects
that should be considered to be equal, you should supply a key
generation function. Otherwise, you have to make sure that your arrays
contain unique references.
For instance, consider this example:
package Person;
sub new
{
my $package = shift;
return bless { name => '', ssn => '', @_ }, $package;
}
sub clone
{
my $old = shift;
my $new = bless { %$old }, ref($old);
}
sub hash
{
return shift()->{'ssn'};
}
my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' );
my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' );
my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' );
my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' );
my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' );
If you did this:
my $array1 = [ $person1, $person2, $person4 ];
my $array2 = [ $person1, $person3, $person4, $person5 ];
Algorithm::Diff::diff( $array1, $array2 );
everything would work out OK (each of the objects would be converted
into a string like "Person=HASH(0x82425b0)" for comparison).
But if you did this:
my $array1 = [ $person1, $person2, $person4 ];
my $array2 = [ $person1, $person3, $person4->clone(), $person5 ];
Algorithm::Diff::diff( $array1, $array2 );
$person4 and $person4->clone() (which have the same name and SSN)
would be seen as different objects. If you wanted them to be considered
equivalent, you would have to pass in a key generation function:
my $array1 = [ $person1, $person2, $person4 ];
my $array2 = [ $person1, $person3, $person4->clone(), $person5 ];
Algorithm::Diff::diff( $array1, $array2, \&Person::hash );
This would use the 'ssn' field in each Person as a comparison key, and
so would consider $person4 and $person4->clone() as equal.
You may also pass additional parameters to the key generation function
if you wish.
=head1 ERROR CHECKING
If you pass these routines a non-reference and they expect a reference,
they will die with a message.
=head1 AUTHOR
This version released by Tye McQueen (http://perlmonks.org/?node=tye).
=head1 LICENSE
Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved.
Parts by Tye McQueen.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl.
=head1 MAILING LIST
( run in 1.254 second using v1.01-cache-2.11-cpan-5735350b133 )