File Coverage

lib/Text/Levenshtein/BV.pm
Criterion Covered Total %
statement 268 269 99.6
branch 53 54 98.1
condition 56 56 100.0
subroutine 17 17 100.0
pod 8 8 100.0
total 402 404 99.5


line stmt bran cond sub pod time code
1             package Text::Levenshtein::BV;
2              
3 3     3   116604 use strict;
  3         20  
  3         71  
4 3     3   12 use warnings;
  3         5  
  3         89  
5             our $VERSION = '0.08';
6              
7 3     3   15 use utf8;
  3         3  
  3         11  
8              
9             #use standard;
10              
11 3     3   68 use 5.010.001;
  3         9  
12              
13             our $width = int 0.999 + log( ~0 ) / log(2);
14              
15 3     3   1313 use integer;
  3         34  
  3         47  
16 3     3   83 no warnings 'portable'; # for 0xffffffffffffffff
  3         5  
  3         89  
17              
18 3     3   1505 use Data::Dumper;
  3         17227  
  3         6459  
19              
20             our @masks = (
21             0x0000000000000000,
22             0x0000000000000001, 0x0000000000000003, 0x0000000000000007, 0x000000000000000f,
23             0x000000000000001f, 0x000000000000003f, 0x000000000000007f, 0x00000000000000ff,
24             0x00000000000001ff, 0x00000000000003ff, 0x00000000000007ff, 0x0000000000000fff,
25             0x0000000000001fff, 0x0000000000003fff, 0x0000000000007fff, 0x000000000000ffff,
26             0x000000000001ffff, 0x000000000003ffff, 0x000000000007ffff, 0x00000000000fffff,
27             0x00000000001fffff, 0x00000000003fffff, 0x00000000007fffff, 0x0000000000ffffff,
28             0x0000000001ffffff, 0x0000000003ffffff, 0x0000000007ffffff, 0x000000000fffffff,
29             0x000000001fffffff, 0x000000003fffffff, 0x000000007fffffff, 0x00000000ffffffff,
30             0x00000001ffffffff, 0x00000003ffffffff, 0x00000007ffffffff, 0x0000000fffffffff,
31             0x0000001fffffffff, 0x0000003fffffffff, 0x0000007fffffffff, 0x000000ffffffffff,
32             0x000001ffffffffff, 0x000003ffffffffff, 0x000007ffffffffff, 0x00000fffffffffff,
33             0x00001fffffffffff, 0x00003fffffffffff, 0x00007fffffffffff, 0x0000ffffffffffff,
34             0x0001ffffffffffff, 0x0003ffffffffffff, 0x0007ffffffffffff, 0x000fffffffffffff,
35             0x001fffffffffffff, 0x003fffffffffffff, 0x007fffffffffffff, 0x00ffffffffffffff,
36             0x01ffffffffffffff, 0x03ffffffffffffff, 0x07ffffffffffffff, 0x0fffffffffffffff,
37             0x1fffffffffffffff, 0x3fffffffffffffff, 0x7fffffffffffffff, 0xffffffffffffffff,
38             );
39              
40             sub new {
41 8     8 1 10197 my $class = shift;
42              
43             return bless(
44 8 100       39 @_ ? (@_ > 1 ? {@_} : {%{$_[0]}} ) : {}, $class
  2 100       11  
45             );
46             }
47              
48             sub SES {
49 844     844 1 4050704 my ( $self, $a, $b ) = @_;
50              
51 844 100 100     856 if ( !scalar(@{$a}) && !scalar(@{$b}) ) { return [] }
  844         1702  
  31         73  
  3         10  
52              
53 841         978 my ( $amin, $amax, $bmin, $bmax ) = ( 0, $#{$a}, 0, $#{$b} );
  841         950  
  841         1635  
54              
55 841         872 if (1) {
56 841   100     3479 while ( $amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin] ) {
      100        
57 696         632 $amin++;
58 696         1705 $bmin++;
59             }
60 841   100     2664 while ( $amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax] ) {
      100        
61 544         467 $amax--;
62 544         1331 $bmax--;
63             }
64             }
65              
66 841 100 100     2723 if ( ( $amax < $amin ) && ( $bmax < $bmin ) ) {
    100          
    100          
67             return [
68 30         34 ( map { [ $_, $_ ] } (0 .. $#{$b}) ),
  86         166  
  30         45  
69             ];
70             }
71             elsif ( ( $amax < $amin ) ) {
72             return [
73 200         338 ( map { [ $_, $_ ] } (0 .. ( $bmin - 1 )) ),
74 363         536 ( map { [ '-1', $_ ] } ($bmin .. $bmax) ),
75 171         300 ( map { [ ++$amax, $_ ] } ($bmax+1 .. $#{$b}) ),
  173         402  
  171         364  
76             ];
77             }
78             elsif ( ( $bmax < $bmin ) ) {
79             return [
80 204         340 ( map { [ $_, $_ ] } (0 .. ( $amin - 1 )) ),
81 369         561 ( map { [ $_, '-1' ] } ($amin .. $amax) ),
82 175         357 ( map { [ $_, ++$bmax ] } ($amax+1 .. $#{$a}) ),
  177         446  
  175         373  
83             ];
84             }
85              
86 465         454 my $positions;
87              
88 465 100       698 if ( ( $amax - $amin ) < $width ) {
89 438         2384 $positions->{ $a->[ $_ + $amin ] } |= 1 << $_ for 0 .. ( $amax - $amin );
90              
91 438         574 my $VPs = [];
92 438         412 my $VNs = [];
93 438         420 my $VP = ~0;
94 438         376 my $VN = 0;
95              
96 438         461 my ( $PM, $X, $D0, $HN, $HP );
97              
98             # outer loop [HN02] Fig. 7
99 438         593 for my $j ( $bmin .. $bmax ) {
100 2361   100     3693 $PM = $positions->{ $b->[$j] } // 0;
101 2361         1919 $X = $PM | $VN;
102 2361         2170 $D0 = ( ( $VP + ( $X & $VP ) ) ^ $VP ) | $X;
103 2361         1948 $HN = $VP & $D0;
104 2361         2051 $HP = $VN | ~( $VP | $D0 );
105 2361         1965 $X = ( $HP << 1 ) | 1;
106 2361         1871 $VN = $X & $D0;
107 2361         1991 $VP = ( $HN << 1 ) | ~( $X | $D0 );
108              
109 2361         2261 $VPs->[$j-$bmin] = $VP;
110 2361         2477 $VNs->[$j-$bmin] = $VN;
111             }
112             return [
113 204         498 ( map { [ $_, $_ ] } (0 .. ($bmin-1)) ),
114             _backtrace( $VPs, $VNs, $amin, $amax, $bmin, $bmax ),
115 438         958 ( map { [ ++$amax, $_ ] } (($bmax+1) .. $#{$b}) ),
  192         819  
  438         1526  
116             ];
117             }
118             else {
119              
120 27         71 my $m = $amax - $amin + 1;
121 27         38 my $diff = $m;
122              
123 27         57 my $kmax = ($m) / $width;
124 27 100       78 $kmax++ if ( ($m) % $width );
125              
126             $positions->{ $a->[ $_ + $amin ] }->[ $_ / $width ] |= 1 << ( $_ % $width )
127 27         2455 for 0 .. ( $amax - $amin );
128              
129 27         54 my @mask;
130              
131 27         108 $mask[$_] = 0 for ( 0 .. $kmax - 1 );
132 27         73 $mask[ $kmax - 1 ] = 1 << ( ( $m - 1 ) % $width );
133              
134 27         39 my @VPs;
135 27         811 $VPs[ $_ / $width ] |= 1 << ( $_ % $width ) for 0 .. $m - 1;
136              
137 27         44 my @VNs;
138 27         86 $VNs[$_] = 0 for ( 0 .. $kmax - 1 );
139              
140 27         53 my $VPS = [];
141 27         44 my $VNS = [];
142              
143 27         107 my ( $PM, $X, $D0, $HN, $HP );
144              
145 27         0 my $HNcarry;
146 27         0 my $HPcarry;
147              
148 27         50 for my $j ( $bmin .. $bmax ) {
149              
150 2838         2173 $HNcarry = 0;
151 2838         2044 $HPcarry = 1;
152 2838         3247 for ( my $k = 0; $k < $kmax; $k++ ) {
153 9662   100     14947 $PM = $positions->{ $b->[$j] }->[$k] // 0;
154 9662         8487 $X = $PM | $HNcarry | $VNs[$k];
155 9662         9220 $D0 = ( ( $VPs[$k] + ( $X & $VPs[$k] ) ) ^ $VPs[$k] ) | $X;
156 9662         8066 $HN = $VPs[$k] & $D0;
157 9662         8301 $HP = $VNs[$k] | ~( $VPs[$k] | $D0 );
158 9662         7664 $X = ( $HP << 1 ) | $HPcarry;
159 9662         7999 $HPcarry = $HP >> ( $width - 1 ) & 1;
160 9662         7904 $VNs[$k] = ( $X & $D0 );
161 9662         8796 $VPs[$k] = ( $HN << 1 ) | ($HNcarry) | ~( $X | $D0 );
162              
163 9662         10827 $VPS->[$j-$bmin][$k] = $VPs[$k];
164 9662         9910 $VNS->[$j-$bmin][$k] = $VNs[$k];
165              
166 9662         12560 $HNcarry = $HN >> ( $width - 1 ) & 1;
167             }
168             }
169             return [
170 2         7 ( map { [ $_, $_ ] } (0 .. ($bmin-1)) ),
171             _backtrace2( $VPS, $VNS, $amin, $amax, $bmin, $bmax, $kmax ),
172 27         158 ( map { [ ++$amax, $_ ] } (($bmax+1) .. $#{$b}) ),
  2         32  
  27         1011  
173             ];
174             }
175             }
176              
177             # Hyyrö, Heikki. (2004). A Note on Bit-Parallel Alignment Computation. 79-87.
178             # Fig. 3
179             sub _backtrace {
180 438     438   674 my ( $VPs, $VNs, $amin, $amax, $bmin, $bmax ) = @_;
181              
182             # recover alignment
183 438         416 my $i = $amax;
184 438         363 my $j = $bmax;
185              
186 438         420 my @ses = ();
187              
188 438         474 my $none = '-1';
189              
190 438   100     1163 while ( $i >= $amin && $j >= $bmin ) {
191              
192 2367 100       2967 if ( $VPs->[$j-$bmin] & ( 1 << ($i-$amin) ) ) {
193 283         413 unshift @ses, [ $i, $none ];
194 283         568 $i--;
195             }
196             else {
197 2084 100 100     3977 if ( ( $j > $bmin ) && ( $VNs->[ $j - $bmin - 1 ]
198             & ( 1 << ($i-$amin) ) ) ) {
199 848         1319 unshift @ses, [ $none, $j ];
200 848         1575 $j--;
201             }
202             else {
203 1236         1710 unshift @ses, [ $i, $j ];
204 1236         1061 $i--;
205 1236         2274 $j--;
206             }
207             }
208             }
209              
210 438         570 while ( $i >= $amin ) {
211 66         89 unshift @ses, [ $i + $amin, $none ];
212 66         84 $i--;
213             }
214 438         537 while ( $j >= $bmin ) {
215 277         362 unshift @ses, [ $none, $j ];
216 277         320 $j--;
217             }
218              
219 438         869 return @ses;
220             }
221              
222             sub _backtrace2 {
223 27     27   77 my ( $VPs, $VNs, $amin, $amax, $bmin, $bmax, $kmax ) = @_;
224              
225             # recover alignment
226 27         45 my $i = $amax;
227 27         34 my $j = $bmax;
228              
229 27         46 my @ses = ();
230              
231 27         50 my $none = '-1';
232              
233 27   100     113 while ( $i >= $amin && $j >= $bmin ) {
234 4180         3487 my $k = ($i - $amin) / $width;
235              
236 4180 100       5071 if ( $VPs->[$j-$bmin]->[$k] & ( 1 << ( ($i - $amin) % $width ) ) ) {
237 1348         1729 unshift @ses, [ $i, $none ];
238 1348         2413 $i--;
239             }
240             else {
241 2832 100 100     5505 if ( ( $j > $bmin ) && ( $VNs->[ $j - $bmin - 1 ]->[$k]
242             & ( 1 << ( ($i - $amin) % $width ) ) ) ) {
243 465         754 unshift @ses, [ $none, $j ];
244 465         924 $j--;
245             }
246             else {
247 2367         3491 unshift @ses, [ $i, $j ];
248 2367         1920 $i--;
249 2367         4421 $j--;
250             }
251             }
252             }
253 27         57 while ( $i >= $amin ) {
254 122         130 unshift @ses, [ $i, $none ];
255 122         131 $i--;
256             }
257 27         54 while ( $j >= $bmin ) {
258 6         14 unshift @ses, [ $none, $j ];
259 6         10 $j--;
260             }
261              
262 27         340 return @ses;
263             }
264              
265             # [HN02] Fig. 3 -> Fig. 7
266             sub distance {
267 812     812 1 4144190 my ( $self, $a, $b ) = @_;
268              
269 812         1015 my ( $amin, $amax, $bmin, $bmax ) = ( 0, $#{$a}, 0, $#{$b} );
  812         1070  
  812         1096  
270              
271 812         1026 if (1) {
272 812   100     3749 while ( $amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin] ) {
      100        
273 678         630 $amin++;
274 678         1745 $bmin++;
275             }
276 812   100     2741 while ( $amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax] ) {
      100        
277 533         494 $amax--;
278 533         1248 $bmax--;
279             }
280             }
281              
282             # if one of the sequences is a complete subset of the other,
283             # return difference of lengths.
284 812 100 100     1895 if ( ( $amax < $amin ) || ( $bmax < $bmin ) ) { return abs( @{$a} - @{$b} ); }
  369         300  
  369         345  
  369         1524  
285              
286 443         424 my $positions;
287              
288 443 100       835 if ( ( $amax - $amin ) < $width ) {
289              
290 418         2095 $positions->{ $a->[ $_ + $amin ] } |= 1 << $_ for 0 .. ( $amax - $amin );
291              
292 418         602 my $m = $amax - $amin + 1;
293 418         371 my $diff = $m;
294              
295 418         431 my $m_mask = 1 << $m - 1;
296              
297 418         376 my $VP = 0;
298              
299 418         429 $VP = $masks[$m]; # mask from cached table
300              
301 418         353 my $VN = 0;
302              
303 418         435 my ( $PM, $X, $D0, $HN, $HP );
304              
305             # outer loop [HN02] Fig. 7
306             # 22 instructions
307 418         585 for my $j ( $bmin .. $bmax ) {
308 2291   100     4311 $PM = $positions->{ $b->[$j] } // 0;
309 2291         2035 $X = $PM | $VN;
310 2291         2617 $D0 = ( ( $VP + ( $X & $VP ) ) ^ $VP ) | $X;
311 2291         1844 $HN = $VP & $D0;
312 2291         1955 $HP = $VN | ~( $VP | $D0 );
313 2291         1982 $X = ( $HP << 1 ) | 1;
314 2291         1791 $VN = $X & $D0;
315 2291         2049 $VP = ( $HN << 1 ) | ~( $X | $D0 );
316              
317 2291 100       2975 if ( $HP & $m_mask ) { $diff++; }
  995 100       873  
318 320         310 elsif ( $HN & $m_mask ) { $diff--; }
319              
320             }
321 418         2171 return $diff;
322             }
323             else {
324              
325 25         69 my $m = $amax - $amin + 1;
326 25         75 my $diff = $m;
327              
328 25         68 my $kmax = ($m) / $width;
329 25 100       81 $kmax++ if ( ($m) % $width );
330              
331             # m * 3
332             $positions->{ $a->[ $_ + $amin ] }->[ $_ / $width ] |= 1 << ( $_ % $width )
333 25         2484 for 0 .. ( $amax - $amin );
334              
335 25         52 my @mask;
336              
337 25         98 $mask[$_] = 0 for ( 0 .. $kmax - 1 );
338 25         79 $mask[ $kmax - 1 ] = 1 << ( ( $m - 1 ) % $width );
339              
340 25         39 my @VPs;
341 25         831 $VPs[ $_ / $width ] |= 1 << ( $_ % $width ) for 0 .. $m - 1;
342              
343 25         42 my @VNs;
344 25         99 $VNs[$_] = 0 for ( 0 .. $kmax - 1 );
345              
346 25         85 my ( $PM, $X, $D0, $HN, $HP );
347              
348 25         0 my $HNcarry;
349 25         0 my $HPcarry;
350              
351 25         40 for my $j ( $bmin .. $bmax ) {
352              
353 2836         2246 $HNcarry = 0;
354 2836         2039 $HPcarry = 1;
355 2836         3426 for ( my $k = 0; $k < $kmax; $k++ ) {
356 9658   100     15531 $PM = $positions->{ $b->[$j] }->[$k] // 0;
357 9658         8113 $X = $PM | $HNcarry | $VNs[$k];
358 9658         9294 $D0 = ( ( $VPs[$k] + ( $X & $VPs[$k] ) ) ^ $VPs[$k] ) | $X;
359 9658         7901 $HN = $VPs[$k] & $D0;
360 9658         8552 $HP = $VNs[$k] | ~( $VPs[$k] | $D0 );
361 9658         7625 $X = ( $HP << 1 ) | $HPcarry;
362 9658         8312 $HPcarry = $HP >> ( $width - 1 ) & 1;
363 9658         8045 $VNs[$k] = ( $X & $D0 );
364 9658         9017 $VPs[$k] = ( $HN << 1 ) | ($HNcarry) | ~( $X | $D0 );
365 9658         8271 $HNcarry = $HN >> ( $width - 1 ) & 1;
366              
367 9658 100       16696 if ( $HP & $mask[$k] ) { $diff++; }
  335 100       497  
368 752         1034 elsif ( $HN & $mask[$k] ) { $diff--; }
369             }
370             }
371 25         714 return $diff;
372             }
373             }
374              
375             sub sequences2hunks {
376 30     30 1 85 my ( $self, $a, $b ) = @_;
377              
378 30         32 return [ map { [ $a->[$_], $b->[$_] ] } 0 .. $#{$a} ];
  148         220  
  30         47  
379             }
380              
381             sub hunks2sequences {
382 30     30 1 52 my ( $self, $hunks ) = @_;
383              
384 30         34 my $a = [];
385 30         37 my $b = [];
386              
387 30         28 for my $hunk (@{$hunks}) {
  30         36  
388 148         112 push @{$a}, $hunk->[0];
  148         157  
389 148         118 push @{$b}, $hunk->[1];
  148         164  
390             }
391 30         55 return ( $a, $b );
392             }
393              
394             sub sequence2char {
395 60     60 1 170 my ( $self, $a, $sequence, $gap ) = @_;
396              
397 60 100       85 $gap = ( defined $gap ) ? $gap : '_';
398              
399 60 100       55 return [ map { ( $_ >= 0 ) ? $a->[$_] : $gap } @{$sequence} ];
  296         462  
  60         62  
400             }
401              
402             sub hunks2distance {
403 844     844 1 1392 my ( $self, $a, $b, $hunks ) = @_;
404              
405 844         806 my $distance = 0;
406              
407 844 100       846 if ( scalar(@{$hunks} ) == 0) { return 0; }
  844         1325  
  3         24  
408              
409 841         837 for my $hunk ( @{$hunks} ) {
  841         997  
410 8990 50 100     6675 if ( scalar(@{$hunk} ) == 0) { next; }
  8990 100       21617  
  0 100       0  
411 4147         3912 elsif ( ( $hunk->[0] < 0 ) || ( $hunk->[1] < 0 ) ) { $distance++ }
412 2234         2125 elsif ( $a->[ $hunk->[0] ] ne $b->[ $hunk->[1] ] ) { $distance++ }
413             }
414 841         3445 return $distance;
415             }
416              
417             sub hunks2char {
418 30     30 1 80 my ( $self, $a, $b, $hunks ) = @_;
419              
420 30         31 my $chars = [];
421              
422 30         30 for my $hunk (@{$hunks}) {
  30         42  
423 148 100       203 my $char1 = ( $hunk->[0] >= 0 ) ? $a->[ $hunk->[0] ] : '_';
424 148 100       168 my $char2 = ( $hunk->[1] >= 0 ) ? $a->[ $hunk->[1] ] : '_';
425              
426 148         118 push @{$chars}, [ $char1, $char2 ];
  148         241  
427             }
428 30         151 return $chars;
429             }
430              
431             1;
432              
433             __END__