File Coverage

lib/Text/Levenshtein/BV.pm
Criterion Covered Total %
statement 198 219 90.4
branch 36 44 81.8
condition 43 44 97.7
subroutine 12 16 75.0
pod 8 8 100.0
total 297 331 89.7


line stmt bran cond sub pod time code
1             package Text::Levenshtein::BV;
2              
3 3     3   121262 use strict;
  3         23  
  3         75  
4 3     3   16 use warnings;
  3         3  
  3         101  
5             our $VERSION = '0.07';
6              
7 3     3   12 use utf8;
  3         5  
  3         12  
8              
9 3     3   115 use 5.010001;
  3         20  
10              
11             our $width = int 0.999+log(~0)/log(2);
12              
13 3     3   1366 use integer;
  3         38  
  3         10  
14 3     3   84 no warnings 'portable'; # for 0xffffffffffffffff
  3         4  
  3         6082  
15              
16             our @masks = (
17             0x0000000000000000,
18             0x0000000000000001,0x0000000000000003,0x0000000000000007,0x000000000000000f,
19             0x000000000000001f,0x000000000000003f,0x000000000000007f,0x00000000000000ff,
20             0x00000000000001ff,0x00000000000003ff,0x00000000000007ff,0x0000000000000fff,
21             0x0000000000001fff,0x0000000000003fff,0x0000000000007fff,0x000000000000ffff,
22             0x000000000001ffff,0x000000000003ffff,0x000000000007ffff,0x00000000000fffff,
23             0x00000000001fffff,0x00000000003fffff,0x00000000007fffff,0x0000000000ffffff,
24             0x0000000001ffffff,0x0000000003ffffff,0x0000000007ffffff,0x000000000fffffff,
25             0x000000001fffffff,0x000000003fffffff,0x000000007fffffff,0x00000000ffffffff,
26             0x00000001ffffffff,0x00000003ffffffff,0x00000007ffffffff,0x0000000fffffffff,
27             0x0000001fffffffff,0x0000003fffffffff,0x0000007fffffffff,0x000000ffffffffff,
28             0x000001ffffffffff,0x000003ffffffffff,0x000007ffffffffff,0x00000fffffffffff,
29             0x00001fffffffffff,0x00003fffffffffff,0x00007fffffffffff,0x0000ffffffffffff,
30             0x0001ffffffffffff,0x0003ffffffffffff,0x0007ffffffffffff,0x000fffffffffffff,
31             0x001fffffffffffff,0x003fffffffffffff,0x007fffffffffffff,0x00ffffffffffffff,
32             0x01ffffffffffffff,0x03ffffffffffffff,0x07ffffffffffffff,0x0fffffffffffffff,
33             0x1fffffffffffffff,0x3fffffffffffffff,0x7fffffffffffffff,0xffffffffffffffff,
34             );
35              
36             sub new {
37 8     8 1 11159 my $class = shift;
38              
39 8 100 66     61 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  2 100       14  
40             }
41              
42             sub SES {
43 812     812 1 4078848 my ($self, $a, $b) = @_;
44              
45 812 100 100     1686 if ( !scalar(@$a) && !scalar(@$b) ) { return [] }
  2         9  
46              
47 810         1341 my ($amin, $amax, $bmin, $bmax) = (0, $#$a, 0, $#$b);
48              
49             # NOTE: prefix / suffix optimisation does not work reliable yet
50 810         846 if (0) {
51             while ($amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin]) {
52             $amin++;
53             $bmin++;
54             }
55             while ($amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax]) {
56             $amax--;
57             $bmax--;
58             }
59              
60             ##print '$amin: ',$amin, ' $amax: ',$amax, ' $bmin: ',$bmin, ' $bmax: ',$bmax, "\n";
61             # if one of the sequences is a complete subset of the other
62              
63             if ( ($amax < $amin) && ($bmax < $bmin) ) {
64             return [
65             map( [$_ => $_], 0 .. $#$b ),
66             ];
67             }
68             elsif ( ($amax < $amin) ) {
69             return [
70             map( [$_ => $_], 0 .. ($bmin-1) ),
71             map( ['-1' => $_], $bmin .. $bmax ),
72             map( [++$amax => $_], ($bmax+1) .. $#$b )
73             ];
74             }
75             elsif ( ($bmax < $bmin) ) {
76             return [
77             map( [$_ => $_], 0 .. ($amin-1) ),
78             map( [$_ => '-1'], $amin .. $amax ),
79             map( [$_ => ++$bmax], ($amax+1) .. $#$a )
80             ];
81             }
82             }
83              
84 810         825 my $positions;
85              
86 810 100       1389 if (($amax - $amin) < $width ) {
87 783         3702 $positions->{$a->[$_+$amin]} |= 1 << $_ for 0..($amax-$amin);
88              
89 783         1040 my $VPs = [];
90 783         957 my $VNs = [];
91 783         836 my $VP = ~0;
92 783         747 my $VN = 0;
93              
94 783         788 my ($PM, $X, $D0, $HN, $HP);
95              
96             # outer loop [HN02] Fig. 7
97 783         1047 for my $j ( $bmin .. $bmax ) {
98 3718   100     6157 $PM = $positions->{$b->[$j]} // 0;
99 3718         3409 $X = $PM | $VN;
100 3718         3447 $D0 = (($VP + ($X & $VP)) ^ $VP) | $X;
101 3718         3055 $HN = $VP & $D0;
102 3718         3305 $HP = $VN | ~($VP|$D0);
103 3718         3222 $X = ($HP << 1) | 1;
104 3718         3006 $VN = $X & $D0;
105 3718         3465 $VP = ($HN << 1) | ~($X | $D0);
106 3718         3472 $VPs->[$j] = $VP;
107 3718         3726 $VNs->[$j] = $VN;
108             }
109             return [
110             #map( [$_ => $_], 0 .. ($bmin-1) ) ,
111 783         1242 _backtrace($VPs, $VNs, $amin, $amax, $bmin, $bmax),
112             #map( [++$amax => $_], ($bmax+1) .. $#$b )
113             ];
114             }
115             else {
116              
117 27         81 my $m = $amax-$amin +1;
118 27         50 my $diff = $m;
119              
120 27         64 my $kmax = ($m) / $width;
121 27 100       85 $kmax++ if (($m) % $width);
122              
123 27         2805 $positions->{$a->[$_+$amin]}->[$_/$width] |= 1 << ($_ % $width) for 0..($amax-$amin);
124              
125 27         71 my @mask;
126              
127 27         103 $mask[$_] = 0 for (0..$kmax-1);
128 27         62 $mask[$kmax-1] = 1 << (($m-1) % $width);
129              
130 27         41 my @VPs;
131 27         875 $VPs[$_ / $width] |= 1 << ($_ % $width) for 0..$m-1;
132              
133 27         38 my @VNs;
134 27         74 $VNs[$_] = 0 for (0..$kmax-1);
135              
136 27         62 my $VPS = [];
137 27         58 my $VNS = [];
138              
139 27         92 my ($PM,$X,$D0,$HN,$HP);
140              
141 27         0 my $HNcarry;
142 27         0 my $HPcarry;
143              
144 27         53 for my $j ( $bmin .. $bmax ) {
145              
146 2979         2378 $HNcarry = 0;
147 2979         2296 $HPcarry = 1;
148 2979         3570 for (my $k=0; $k < $kmax; $k++ ) {
149 9944   100     16834 $PM = $positions->{$b->[$j]}->[$k] // 0;
150 9944         8686 $X = $PM | $HNcarry | $VNs[$k];
151 9944         9775 $D0 = (($VPs[$k] + ($X & $VPs[$k])) ^ $VPs[$k]) | $X;
152 9944         8045 $HN = $VPs[$k] & $D0;
153 9944         8892 $HP = $VNs[$k] | ~($VPs[$k] | $D0);
154 9944         8228 $X = ($HP << 1) | $HPcarry;
155 9944         8948 $HPcarry = $HP >> ($width-1) & 1;
156 9944         7999 $VNs[$k] = ($X & $D0);
157 9944         8789 $VPs[$k] = ($HN << 1) | ($HNcarry) | ~($X | $D0);
158              
159 9944         11819 $VPS->[$j][$k] = $VPs[$k];
160 9944         10181 $VNS->[$j][$k] = $VNs[$k];
161              
162 9944         12970 $HNcarry = $HN >> ($width-1) & 1;
163             }
164             }
165             return [
166             #map([$_ => $_], 0 .. ($bmin-1)),
167 27         133 _backtrace2($VPS, $VNS, $amin, $amax, $bmin, $bmax),
168             #map([++$amax => $_], ($bmax+1) .. $#$b)
169             ];
170             }
171             }
172              
173             # Hyyrö, Heikki. (2004). A Note on Bit-Parallel Alignment Computation. 79-87.
174             # Fig. 3
175             sub _backtrace {
176 783     783   1116 my ($VPs, $VNs, $amin, $amax, $bmin, $bmax) = @_;
177              
178             # recover alignment
179 783         722 my $i = $amax;
180 783         704 my $j = $bmax;
181              
182 783         739 my @ses = ();
183              
184 783         886 my $none = '-1';
185              
186 783   100     1990 while ($i >= $amin && $j >= $bmin) {
187 3818 100       4529 if ($VPs->[$j] & (1<<$i)) {
188 467         692 unshift @ses,[$i, $none];
189 467         957 $i--;
190             }
191             else {
192 3351 100 100     6199 if (($j > 0) && ($VNs->[$j-1] & (1<<$i))) {
193 1117         2000 unshift @ses, [$none, $j];
194 1117         2229 $j--;
195             }
196             else {
197 2234         3377 unshift @ses, [$i, $j];
198 2234         1900 $i--;$j--;
  2234         4095  
199             }
200             }
201             }
202 783         1067 while ($i >= $amin) {
203 210         284 unshift @ses,[$i+$amin,$none];
204 210         269 $i--;
205             }
206 783         994 while ($j >= $bmin) {
207 367         481 unshift @ses,[$none,$j];
208 367         450 $j--;
209             }
210 783         3630 return @ses;
211             }
212              
213             sub _backtrace2 {
214 27     27   80 my ($VPs, $VNs, $amin, $amax, $bmin, $bmax) = @_;
215              
216             # recover alignment
217 27         44 my $i = $amax;
218 27         35 my $j = $bmax;
219              
220 27         67 my @ses = ();
221              
222 27         38 my $none = '-1';
223              
224 27   100     124 while ($i >= $amin && $j >= $bmin) {
225 4290         3693 my $k = $i / $width;
226 4290 100       5524 if ( $VPs->[$j]->[$k] & (1<<($i % $width)) ) {
227 1317         1954 unshift @ses, [$i, $none];
228 1317         2492 $i--;
229             }
230             else {
231 2973 100 100     6413 if ( ($j > 0) && ($VNs->[$j-1]->[$k] & (1<<($i % $width))) ) {
232             ##if ( ($VNs->[$j-1]->[$k] & (1<<($i % $width))) ) {
233 466         816 unshift @ses, [$none, $j];
234 466         955 $j--;
235             }
236             else {
237 2507         4538 unshift @ses, [$i, $j];
238 2507         2119 $i--;$j--;
  2507         4891  
239             }
240             }
241             }
242 27         66 while ($i >= $amin) {
243 10         22 unshift @ses, [$i, $none];
244 10         16 $i--;
245             }
246 27         62 while ($j >= $bmin) {
247 6         15 unshift @ses, [$none, $j];
248 6         19 $j--;
249             }
250 27         1864 return @ses;
251             }
252              
253             # [HN02] Fig. 3 -> Fig. 7
254             sub distance {
255 812     812 1 4024272 my ($self, $a, $b) = @_;
256              
257 812         1359 my ($amin, $amax, $bmin, $bmax) = (0, $#$a, 0, $#$b);
258              
259 812         853 if (1) {
260 812   100     3629 while ($amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin]) {
      100        
261 678         653 $amin++;
262 678         1665 $bmin++;
263             }
264 812   100     2400 while ($amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax]) {
      100        
265 533         465 $amax--;
266 533         1257 $bmax--;
267             }
268             }
269              
270             # if one of the sequences is a complete subset of the other,
271             # return difference of lengths.
272 812 100 100     1884 if (($amax < $amin) || ($bmax < $bmin)) { return abs(@$a - @$b); }
  369         1470  
273              
274 443         449 my $positions;
275              
276 443 100       753 if (($amax - $amin) < $width ) {
277              
278 418         1985 $positions->{$a->[$_+$amin]} |= 1 << $_ for 0..($amax-$amin);
279              
280 418         567 my $m = $amax-$amin +1;
281 418         434 my $diff = $m;
282              
283 418         472 my $m_mask = 1 << $m-1;
284              
285 418         352 my $VP = 0;
286              
287 418         414 $VP = $masks[$m]; # mask from cached table
288              
289 418         370 my $VN = 0;
290              
291 418         429 my ($PM,$X,$D0,$HN,$HP);
292              
293             # outer loop [HN02] Fig. 7
294             # 22 instructions
295 418         535 for my $j ( $bmin .. $bmax ) {
296 2291   100     3814 $PM = $positions->{$b->[$j]} // 0;
297 2291         1926 $X = $PM | $VN;
298 2291         2091 $D0 = (($VP + ($X & $VP)) ^ $VP) | $X;
299 2291         1861 $HN = $VP & $D0;
300 2291         2004 $HP = $VN | ~($VP|$D0);
301 2291         1973 $X = ($HP << 1) | 1;
302 2291         1888 $VN = $X & $D0;
303 2291         2032 $VP = ($HN << 1) | ~($X | $D0);
304              
305 2291 100       2961 if ($HP & $m_mask) { $diff++; }
  995 100       901  
306 320         287 elsif ($HN & $m_mask) { $diff--; }
307              
308             }
309 418         2038 return $diff;
310             }
311             else {
312              
313 25         61 my $m = $amax-$amin +1;
314 25         45 my $diff = $m;
315              
316 25         35 my $kmax = ($m) / $width;
317 25 100       84 $kmax++ if (($m) % $width);
318              
319             # m * 3
320 25         2205 $positions->{$a->[$_+$amin]}->[$_ / $width] |= 1 << ($_ % $width) for 0..($amax-$amin);
321              
322 25         56 my @mask;
323              
324 25         80 $mask[$_] = 0 for (0..$kmax-1);
325 25         61 $mask[$kmax-1] = 1 << (($m-1) % $width);
326              
327 25         43 my @VPs;
328 25         805 $VPs[$_ / $width] |= 1 << ($_ % $width) for 0..$m-1;
329              
330 25         39 my @VNs;
331 25         127 $VNs[$_] = 0 for (0..$kmax-1);
332              
333 25         77 my ($PM,$X,$D0,$HN,$HP);
334              
335 25         0 my $HNcarry;
336 25         0 my $HPcarry;
337              
338 25         70 for my $j ( $bmin .. $bmax ) {
339              
340 2836         2235 $HNcarry = 0;
341 2836         2002 $HPcarry = 1;
342 2836         3168 for (my $k=0; $k < $kmax; $k++ ) {
343 9658   100     15467 $PM = $positions->{$b->[$j]}->[$k] // 0;
344 9658         8752 $X = $PM | $HNcarry | $VNs[$k];
345 9658         9147 $D0 = (($VPs[$k] + ($X & $VPs[$k])) ^ $VPs[$k]) | $X;
346 9658         7949 $HN = $VPs[$k] & $D0;
347 9658         8415 $HP = $VNs[$k] | ~($VPs[$k] | $D0);
348 9658         8165 $X = ($HP << 1) | $HPcarry;
349 9658         8481 $HPcarry = $HP >> ($width-1) & 1;
350 9658         7835 $VNs[$k] = ($X & $D0);
351 9658         8351 $VPs[$k] = ($HN << 1) | ($HNcarry) | ~($X | $D0);
352 9658         7941 $HNcarry = $HN >> ($width-1) & 1;
353              
354 9658 100       16211 if ($HP & $mask[$k]) { $diff++; }
  335 100       465  
355 752         969 elsif ($HN & $mask[$k]) { $diff--; }
356             }
357             }
358 25         474 return $diff;
359             }
360             }
361              
362             sub sequences2hunks {
363 0     0 1 0 my ($self, $a, $b) = @_;
364 0         0 return [ map { [ $a->[$_], $b->[$_] ] } 0..$#$a ];
  0         0  
365             }
366              
367             sub hunks2sequences {
368 0     0 1 0 my ($self, $hunks) = @_;
369              
370 0         0 my $a = [];
371 0         0 my $b = [];
372              
373 0         0 for my $hunk (@$hunks) {
374 0         0 push @$a, $hunk->[0];
375 0         0 push @$b, $hunk->[1];
376             }
377 0         0 return ($a,$b);
378             }
379              
380             sub sequence2char {
381 0     0 1 0 my ($self, $a, $sequence, $gap) = @_;
382              
383 0 0       0 $gap = (defined $gap) ? $gap : '_';
384              
385 0 0       0 return [ map { ($_ >= 0) ? $a->[$_] : $gap } @$sequence ];
  0         0  
386             }
387              
388             sub hunks2distance {
389 812     812 1 1335 my ($self, $a, $b, $hunks) = @_;
390              
391 812         757 my $distance = 0;
392              
393 812         1125 for my $hunk (@$hunks) {
394 8701 100 100     19000 if (($hunk->[0] < 0) || ($hunk->[1] < 0)) { $distance++ }
  3960 100       3931  
395 2191         2100 elsif ($a->[$hunk->[0]] ne $b->[$hunk->[1]]) { $distance++ }
396             }
397 812         3356 return $distance;
398             }
399              
400             sub hunks2char {
401 0     0 1   my ($self, $a, $b, $hunks) = @_;
402              
403 0           my $chars = [];
404              
405 0           for my $hunk (@$hunks) {
406 0 0         my $char1 = ($hunk->[0] >= 0) ? $a->[$hunk->[0]] : '_';
407 0 0         my $char2 = ($hunk->[1] >= 0) ? $a->[$hunk->[1]] : '_';
408              
409 0           push @$chars, [$char1,$char2];
410             }
411 0           return $chars;
412             }
413              
414             1;
415              
416             __END__